summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.sbt2
-rw-r--r--src/main/scala/forge/Evaluator.scala32
-rw-r--r--src/main/scala/forge/Target.scala78
-rw-r--r--src/main/scala/forge/Util.scala2
-rw-r--r--src/test/scala/forge/ForgeTests.scala28
5 files changed, 100 insertions, 42 deletions
diff --git a/build.sbt b/build.sbt
index 340f4e6c..30b81658 100644
--- a/build.sbt
+++ b/build.sbt
@@ -8,6 +8,8 @@ libraryDependencies += "com.lihaoyi" %% "utest" % "0.6.0" % "test"
testFrameworks += new TestFramework("forge.Framework")
+parallelExecution in Test := false
+
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
diff --git a/src/main/scala/forge/Evaluator.scala b/src/main/scala/forge/Evaluator.scala
index fb45aa32..b0c8bf12 100644
--- a/src/main/scala/forge/Evaluator.scala
+++ b/src/main/scala/forge/Evaluator.scala
@@ -9,16 +9,16 @@ import scala.collection.mutable
class Evaluator(workspacePath: jnio.Path,
enclosingBase: DefCtx){
+ val resultCache = mutable.Map.empty[Target[_], (Int, Any)]
-
- def apply[T](t: Target[T])
- (implicit enclosing: Enclosing): T = {
+ def evaluate(targets: Seq[Target[_]]): Evaluator.Results = {
jnio.Files.createDirectories(workspacePath)
- val sortedTargets = Evaluator.topoSortedTransitiveTargets(Seq(t))
+ val sortedTargets = Evaluator.topoSortedTransitiveTargets(targets)
+ val evaluated = mutable.Buffer.empty[Target[_]]
val results = mutable.Map.empty[Target[_], Any]
for (target <- sortedTargets){
- val inputResults = target.inputs.map(results)
+ val inputResults = target.inputs.map(results).toIndexedSeq
val targetDestPath = target.defCtx.staticEnclosing match{
case Some(enclosingStr) =>
@@ -30,15 +30,19 @@ class Evaluator(workspacePath: jnio.Path,
case None => jnio.Files.createTempDirectory(null)
}
+ val inputsHash = inputResults.hashCode
+ resultCache.get(target) match{
+ case Some((hash, res)) if hash == inputsHash && !target.dirty =>
+ results(target) = res
+ case _ =>
+ evaluated.append(target)
+ val res = target.evaluate(new Args(inputResults, targetDestPath))
+ resultCache(target) = (inputsHash, res)
+ results(target) = res
+ }
-
-
-
- results(target) = target.evaluate(
- new Args(inputResults.toIndexedSeq, targetDestPath)
- )
}
- results(t).asInstanceOf[T]
+ Evaluator.Results(targets.map(results), evaluated)
}
def deleteRec(path: jnio.Path) = {
if (jnio.Files.exists(path)){
@@ -54,21 +58,23 @@ class Evaluator(workspacePath: jnio.Path,
object Evaluator{
+ case class Results(values: Seq[Any], evaluated: Seq[Target[_]])
/**
* Takes the given targets, finds
*/
def topoSortedTransitiveTargets(sourceTargets: Seq[Target[_]]) = {
val transitiveTargetSet = mutable.Set.empty[Target[_]]
+ val transitiveTargets = mutable.Buffer.empty[Target[_]]
def rec(t: Target[_]): Unit = {
if (transitiveTargetSet.contains(t)) () // do nothing
else {
transitiveTargetSet.add(t)
+ transitiveTargets.append(t)
t.inputs.foreach(rec)
}
}
sourceTargets.foreach(rec)
- val transitiveTargets = transitiveTargetSet.toVector
val targetIndices = transitiveTargets.zipWithIndex.toMap
val numberedEdges =
diff --git a/src/main/scala/forge/Target.scala b/src/main/scala/forge/Target.scala
index b2f837a7..acbb8449 100644
--- a/src/main/scala/forge/Target.scala
+++ b/src/main/scala/forge/Target.scala
@@ -1,72 +1,94 @@
package forge
import java.nio.{file => jnio}
-
-trait Target[T]{
+trait TargetOps[T]{ this: Target[T] =>
val defCtx: DefCtx
-
- override def toString = defCtx.staticEnclosing match{
- case None => this.getClass.getSimpleName + "@" + Integer.toHexString(System.identityHashCode(this))
- case Some(s) => this.getClass.getName + "@" + s
- }
- val inputs: Seq[Target[_]]
- def evaluate(args: Args): T
-
def map[V](f: T => V)(implicit defCtx: DefCtx) = {
- Target.Mapped(this, f, defCtx)
+ new Target.Mapped(this, f, defCtx)
}
def zip[V](other: Target[V])(implicit defCtx: DefCtx) = {
- Target.Zipped(this, other, defCtx)
+ new Target.Zipped(this, other, defCtx)
}
def ~[V, R](other: Target[V])
(implicit s: Implicits.Sequencer[T, V, R], defCtx: DefCtx): Target[R] = {
this.zip(other).map(s.apply _ tupled)
}
+ override def toString = defCtx.staticEnclosing match{
+ case None => this.getClass.getSimpleName + "@" + Integer.toHexString(System.identityHashCode(this))
+ case Some(s) => this.getClass.getName + "@" + s
+ }
+}
+trait Target[T] extends TargetOps[T]{
+ /**
+ * Where in the Scala codebase was this target defined?
+ */
+ val defCtx: DefCtx
+ /**
+ * What other Targets does this Target depend on?
+ */
+ val inputs: Seq[Target[_]]
+
+ /**
+ * Evaluate this target
+ */
+ def evaluate(args: Args): T
+
+ /**
+ * Even if this target's inputs did not change, does it need to re-evaluate
+ * anyway? e.g. if the files this target represents on disk changed
+ */
+ def dirty: Boolean = false
+
}
object Target{
- def test(inputs: Target[Int]*)(implicit defCtx: DefCtx) = Test(inputs, defCtx)
+ def test(inputs: Target[Int]*)(implicit defCtx: DefCtx) = new Test(inputs, defCtx)
/**
* A dummy target that takes any number of inputs, and whose output can be
* controlled externally, so you can construct arbitrary dataflow graphs and
* test how changes propagate.
*/
- case class Test(inputs: Seq[Target[Int]], defCtx: DefCtx) extends Target[Int]{
- var counter = 1
- def evaluate(args: Args) = counter + args.args.map(_.asInstanceOf[Int]).sum
+ class Test(val inputs: Seq[Target[Int]], val defCtx: DefCtx) extends Target[Int]{
+ var counter = 0
+ var lastCounter = counter
+ def evaluate(args: Args) = {
+ lastCounter = counter
+ counter + args.args.map(_.asInstanceOf[Int]).sum
+ }
+ override def dirty = lastCounter != counter
}
def traverse[T](source: Seq[Target[T]])(implicit defCtx: DefCtx) = {
- Traverse[T](source, defCtx)
+ new Traverse[T](source, defCtx)
}
- case class Traverse[T](inputs: Seq[Target[T]], defCtx: DefCtx) extends Target[Seq[T]]{
+ class Traverse[T](val inputs: Seq[Target[T]], val defCtx: DefCtx) extends Target[Seq[T]]{
def evaluate(args: Args) = {
for (i <- 0 until args.length)
yield args(i).asInstanceOf[T]
}
}
- case class Mapped[T, V](source: Target[T], f: T => V,
- defCtx: DefCtx) extends Target[V]{
+ class Mapped[T, V](source: Target[T], f: T => V,
+ val defCtx: DefCtx) extends Target[V]{
def evaluate(args: Args) = f(args(0))
val inputs = List(source)
}
- case class Zipped[T, V](source1: Target[T],
- source2: Target[V],
- defCtx: DefCtx) extends Target[(T, V)]{
+ class Zipped[T, V](source1: Target[T],
+ source2: Target[V],
+ val defCtx: DefCtx) extends Target[(T, V)]{
def evaluate(args: Args) = (args(0), args(0))
val inputs = List(source1, source1)
}
- def path(path: jnio.Path)(implicit defCtx: DefCtx) = Path(path, defCtx)
- case class Path(path: jnio.Path, defCtx: DefCtx) extends Target[jnio.Path]{
+ def path(path: jnio.Path)(implicit defCtx: DefCtx) = new Path(path, defCtx)
+ class Path(path: jnio.Path, val defCtx: DefCtx) extends Target[jnio.Path]{
def evaluate(args: Args) = path
val inputs = Nil
}
- case class Subprocess(inputs: Seq[Target[_]],
- command: Args => Seq[String],
- defCtx: DefCtx) extends Target[Subprocess.Result] {
+ class Subprocess(val inputs: Seq[Target[_]],
+ command: Args => Seq[String],
+ val defCtx: DefCtx) extends Target[Subprocess.Result] {
def evaluate(args: Args) = {
jnio.Files.createDirectories(args.dest)
diff --git a/src/main/scala/forge/Util.scala b/src/main/scala/forge/Util.scala
index 89a3eafe..7beffd4e 100644
--- a/src/main/scala/forge/Util.scala
+++ b/src/main/scala/forge/Util.scala
@@ -11,7 +11,7 @@ import scala.collection.JavaConverters._
object Util{
def compileAll(sources: Target[Seq[jnio.Path]])
(implicit defCtx: DefCtx) = {
- Target.Subprocess(
+ new Target.Subprocess(
Seq(sources),
args =>
Seq("javac") ++
diff --git a/src/test/scala/forge/ForgeTests.scala b/src/test/scala/forge/ForgeTests.scala
index 8da52958..a869ae4a 100644
--- a/src/test/scala/forge/ForgeTests.scala
+++ b/src/test/scala/forge/ForgeTests.scala
@@ -52,6 +52,34 @@ object ForgeTests extends TestSuite{
Diamond.down
)
)
+
+ 'evaluate - {
+ def check(targets: Seq[Target[_]],
+ values: Seq[Any],
+ evaluated: Seq[Target[_]]) = {
+ val Evaluator.Results(returnedValues, returnedEvaluated) = evaluator.evaluate(targets)
+ assert(
+ returnedValues == values,
+ returnedEvaluated == evaluated
+ )
+
+ }
+ 'singleton - {
+ import Singleton._
+ // First time the target is evaluated
+ check(Seq(single), values = Seq(0), evaluated = Seq(single))
+ // Second time the value is already cached, so no evaluation needed
+ check(Seq(single), values = Seq(0), evaluated = Seq())
+ Singleton.single.counter += 1
+ // After incrementing the counter, it forces re-evaluation
+ check(Seq(single), values = Seq(1), evaluated = Seq(single))
+ // Then it's cached again
+ check(Seq(single), values = Seq(1), evaluated = Seq())
+ }
+// 'pair - {
+//
+// }
+ }
}
// 'full - {