Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ To provide a DSL for the pure embedding of roles in structured contexts, *SCROLL
## Example ##

```scala
import scroll.Compartment

// A Natural type, the player:
class Person(val firstName: String)

Expand All @@ -47,6 +49,13 @@ new Compartment {
}
```

For user-facing imports, prefer the public `scroll` facade such as `scroll.Compartment`, `scroll.DispatchQuery`, and
`scroll.Many.*` instead of importing from `scroll.internal.*`.

When working with `scroll.MultiCompartment`, the public `scroll.MultiDispatchResult` alias and `sequenceResults`
extension help flatten the nested `Either[SCROLLError, Seq[Either[SCROLLError, E]]]` return shape into
`Either[SCROLLError, Seq[E]]` when all dispatched role invocations succeed.

A more elaborated example can be found [here](https://github.com/max-leuthaeuser/SCROLL/wiki/The-Bank-Example-%28Overview%29) and [here](https://github.com/max-leuthaeuser/SCROLL/wiki/The-Bank-Example-%28Advanced-Role-features%29).

You can find even more examples [here](https://github.com/max-leuthaeuser/SCROLL/tree/master/examples/src/main/scala/scroll/examples).
Expand Down
14 changes: 13 additions & 1 deletion benchmark/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
SCROLL Benchmark
================

This subproject contains the JMH benchmarks used to measure dispatch, caching, and larger end-to-end role scenarios.
It depends on the main `core` module and is compiled as the `benchmark` sbt subproject.

## How to run ##

1) Run `sbt` and switch to the `benchmark` module with `project benchmark`.
1) Run `sbt` and switch to the `benchmark` module with `project benchmark`, or compile it directly with
`benchmark / compile`.

2) Launch `JMH` e.g., for the `BankExample` benchmark with: `Jmh / run -jvmArgs "-Xms10g -Xmx12g -XX:+UseG1GC -XX:ParallelGCThreads=4 -XX:ConcGCThreads=1 -XX:InitiatingHeapOccupancyPercent=70" .*BankBenchmark`

3) To run through the dedicated benchmark runner that also exports results as `.csv` and `.json`, use:
`benchmark / Jmh / run .*BankBenchmark`

The main library now exposes the public `scroll` facade for user-facing code. The benchmark sources intentionally stay
close to the lower-level runtime APIs they measure, so they may still import selected `scroll.internal.*` types.

For GC parameters see: https://www.oracle.com/technetwork/articles/java/g1gc-1984535.html

Expand All @@ -17,6 +26,9 @@ For GC parameters see: https://www.oracle.com/technetwork/articles/java/g1gc-198

Use e.g.: http://jmh.morethan.io/

When benchmarks are started through `RunnerApp`, result files are written to the working directory as
`benchmark_<timestamp>.csv` and `benchmark_<timestamp>.json`.

## Enable Profiling ##

Use e.g., YourKit with: `Jmh / run -jvmArgs "-agentpath:/Applications/YourKit-Java-Profiler-2019.1.app/Contents/Resources/bin/mac/libyjpagent.jnilib=onexit=memory,onexit=snapshot -Xms10g -Xmx12g -XX:+UseG1GC -XX:ParallelGCThreads=4 -XX:ConcGCThreads=1 -XX:InitiatingHeapOccupancyPercent=70" .*BankBenchmark`
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import org.openjdk.jmh.annotations._

import java.util.concurrent.TimeUnit

/** Shared JMH configuration for all SCROLL benchmarks in this subproject.
*
* Concrete benchmarks inherit the same mode, warmup, measurement, fork, and state settings so their results are easier
* to compare across different scenarios.
*/
@BenchmarkMode(Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State

/** Benchmarks for the bank example with configurable player, role, transaction, and caching settings.
*/
object BankBenchmark {

@State(Scope.Benchmark)
Expand All @@ -26,6 +28,8 @@ object BankBenchmark {

}

/** Measures both building and executing the larger bank benchmark scenario.
*/
class BankBenchmark extends AbstractBenchmark with BankBenchmarkConfig.Exhaustive {

import BankBenchmark._
Expand Down
4 changes: 4 additions & 0 deletions benchmark/src/main/scala/scroll/benchmarks/BankExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import scroll.internal.dispatch.DispatchQuery.Bypassing
import scala.collection.mutable.ArrayBuffer
import scala.util.Random

/** End-to-end benchmark fixture modelling a bank with customers, accounts, and money transfers.
*
* The setup exercises role binding, deep dispatch chains, compartment composition, and optional graph caching.
*/
class BankExample {

class Person(val name: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package scroll.benchmarks
import org.openjdk.jmh.annotations._
import org.openjdk.jmh.annotations.Benchmark

/** Benchmarks for comparing cached versus non-cached dispatch over repeated invocations.
*/
object CachingBenchmark {

@State(Scope.Benchmark)
Expand All @@ -27,6 +29,8 @@ object CachingBenchmark {

}

/** Measures the cost of repeated dispatches through cached and non-cached role graphs.
*/
class CachingBenchmark extends AbstractBenchmark {

import CachingBenchmark._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package scroll.benchmarks

import scroll.internal.compartment.impl.Compartment

/** Benchmark fixture comparing cached and non-cached dispatch lookups across a role chain of configurable depth.
*/
class CachingExample {

class Core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import scroll.internal.dispatch.SCROLLDynamic
import java.util.concurrent.TimeUnit
import scala.util.Random

/** Benchmarks for the lightweight no-op forwarding scenario.
*/
object NoopBenchmark {

@State(Scope.Thread)
Expand All @@ -16,6 +18,8 @@ object NoopBenchmark {
@Param(Array("true", "false"))
var cached: Boolean = scala.compiletime.uninitialized

/** Rebuilds the no-op scenario for the current cache mode before each iteration.
*/
@Setup(Level.Iteration)
def setup(): Unit = {
player = new NoopExample(cached).compartment.player
Expand Down
2 changes: 2 additions & 0 deletions benchmark/src/main/scala/scroll/benchmarks/NoopExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import scroll.internal.compartment.impl.Compartment
import scroll.internal.dispatch.DispatchQuery
import scroll.internal.dispatch.DispatchQuery._

/** Minimal benchmark fixture for measuring dispatch overhead when a role only forwards to the base object.
*/
class NoopExample(cached: Boolean) {

val compartment = new NoopCompartment()
Expand Down
4 changes: 4 additions & 0 deletions benchmark/src/main/scala/scroll/benchmarks/RunnerApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import org.openjdk.jmh.runner.options.CommandLineOptions
import java.text.SimpleDateFormat
import java.util.Date

/** Runs JMH from the command line and writes the collected results in machine-readable formats.
*
* Output files are created in the current working directory using a timestamped `benchmark_<timestamp>` prefix.
*/
object RunnerApp {

def main(args: Array[String]): Unit = {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/scroll/DispatchQuery.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scroll

/** Public entry point for dispatch-query builders and helpers such as [[Bypassing]], [[From]], and [[Through]].
*/
object DispatchQuery {
export internal.dispatch.DispatchQuery.*
}
7 changes: 7 additions & 0 deletions core/src/main/scala/scroll/Many.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scroll

/** Public entry point for the unbounded role-group cardinality marker `*`.
*/
object Many {
export internal.util.Many.*
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package scroll.internal.compartment.impl
import scroll.internal.compartment.CompartmentApi
import scroll.internal.dispatch.DispatchQuery
import scroll.internal.dispatch.impl.SCROLLDispatchable
import scroll.internal.errors.SCROLLErrors.InvalidRolePlayer
import scroll.internal.errors.SCROLLErrors.SCROLLError
import scroll.internal.errors.SCROLLErrors.TypeError
import scroll.internal.errors.SCROLLErrors.TypeNotFound
import scroll.internal.graph.RoleGraphProxyApi
Expand Down Expand Up @@ -37,8 +39,8 @@ abstract class AbstractCompartment() extends CompartmentApi {
override lazy val roleGroups: RoleGroupsApi = new RoleGroups(roleGraph)
override lazy val playerEquality: PlayerEqualityApi = new PlayerEquality(roleGraph)

implicit def either2TorException[T](either: Either[?, T]): T =
either.fold(l => throw new RuntimeException(l.toString), r => r)
implicit def either2TorException[E <: SCROLLError, T](either: Either[E, T]): T =
either.fold(err => throw err, identity)

protected def applyDispatchQuery(dispatchQuery: DispatchQuery, on: AnyRef): Seq[AnyRef] =
roleGraph.plays.coreFor(on).lastOption match {
Expand Down Expand Up @@ -104,7 +106,7 @@ abstract class AbstractCompartment() extends CompartmentApi {
case p: IPlayer[?, ?] => rolePlaying.addPlaysRelation[W, R](p.wrapped.asInstanceOf[W], role)
case p: AnyRef => rolePlaying.addPlaysRelation[W, R](p.asInstanceOf[W], role)
case null =>
throw new RuntimeException(s"Only instances of 'IPlayer' or 'AnyRef' are allowed to play roles!")
throw InvalidRolePlayer()
}
this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import scala.reflect.ClassTag
*/
trait MultiCompartment extends AbstractCompartment {

implicit def either2SeqTOrException[T](either: Either[?, Seq[Either[?, T]]]): Seq[T] =
either.fold(left => throw new RuntimeException(left.toString), right => right.map(either2TorException))
implicit def either2SeqTOrException[T](either: Either[SCROLLError, Seq[Either[SCROLLError, T]]]): Seq[T] =
either.fold(err => throw err, _.map(either2TorException))

override def newPlayer[W <: AnyRef: ClassTag](obj: W): MultiPlayer[W] = {
require(null != obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.eclipse.emf.ecore.util.BasicExtendedMetaData
import org.eclipse.emf.ecore.xmi.XMLResource
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl
import scroll.internal.errors.SCROLLErrors.CROMMetaModelLoadFailure

/** Trait providing functionality for importing ecore models.
*/
Expand All @@ -25,7 +26,7 @@ trait ECoreImporter {
val eObject = r.getContents.get(0)
eObject match {
case p: EPackage => val _ = rs.getPackageRegistry.put(p.getNsURI, p)
case _ => throw new IllegalStateException("Meta-Model for CROM could not be loaded!")
case _ => throw CROMMetaModelLoadFailure()
}
}

Expand Down
Loading
Loading