diff --git a/build.sbt b/build.sbt index 9e613ad..349e0bc 100644 --- a/build.sbt +++ b/build.sbt @@ -20,9 +20,12 @@ lazy val graph = (project in file("graph")).settings( name := "fancadegraph", version := "0.1.0", scalaVersion := "2.13.3", + resolvers += Resolver.bintrayRepo("alexknvl", "maven"), libraryDependencies ++= Seq( "org.scala-graph" %% "graph-core" % "1.13.2", + "com.alexknvl" %% "polymorphic" % "0.5.0", ), + addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full), ).dependsOn(scodec) lazy val tagless = (project in file("tagless")).settings( @@ -36,6 +39,7 @@ lazy val tagless = (project in file("tagless")).settings( "org.typelevel" %% "cats-core" % "2.1.1", "org.typelevel" %% "cats-effect" % "2.1.3", "io.chrisdavenport" %% "fuuid" % "0.4.0", + "io.higherkindness" %% "droste-core" % "0.8.0", "org.scalameta" %% "munit" % "0.7.9" % Test, ), testFrameworks += new TestFramework("munit.Framework"), diff --git a/graph/src/main/scala/tf/bug/fancadegraph/Block.scala b/graph/src/main/scala/tf/bug/fancadegraph/Block.scala index 213bfbf..4e04b5b 100644 --- a/graph/src/main/scala/tf/bug/fancadegraph/Block.scala +++ b/graph/src/main/scala/tf/bug/fancadegraph/Block.scala @@ -1,22 +1,14 @@ package tf.bug.fancadegraph +import polymorphic._ import tf.bug.fancadescodec.{Metadata, Position} -case class Block[T]( +case class Block( position: Position, - definition: BlockDefinition[T], - argument: T -)( - implicit ev: Argument[T] + template: Exists[Template] ) { - def metadata: Vector[Metadata] = ev.encode(argument)(position) -} - -object Block { - def apply(position: Position, definition: BlockDefinition[Unit]): Block[Unit] = - new Block( - position, - definition, - () - ) + def metadata: Vector[Metadata] = { + val t = Exists.unwrap(template) + t.ev.encode(t.args)(position) + } } diff --git a/graph/src/main/scala/tf/bug/fancadegraph/Level.scala b/graph/src/main/scala/tf/bug/fancadegraph/Level.scala index 8ae44b4..9dbfeb7 100644 --- a/graph/src/main/scala/tf/bug/fancadegraph/Level.scala +++ b/graph/src/main/scala/tf/bug/fancadegraph/Level.scala @@ -1,14 +1,24 @@ package tf.bug.fancadegraph import cats.data._ +import polymorphic._ import scalax.collection.Graph import scalax.collection.GraphEdge.DiHyperEdge import tf.bug.{fancadescodec => fansc} case class Level( - blocks: Set[Block[_]], + blocks: Set[Block], wires: Graph[Pin, DiHyperEdge] -) +) { + def boundary = + blocks.foldLeft(fansc.Boundary(0, 0, 0)) { + case (fansc.Boundary(lr, du, nf), block) => + val tlr = block.position.lr + Exists.unwrap(block.template).definition.width + val tdu = block.position.du + Exists.unwrap(block.template).definition.height + val tnf = block.position.nf + Exists.unwrap(block.template).definition.depth + fansc.Boundary(lr.max(tlr), du.max(tdu), nf.max(tnf)) + } +} object Level { @@ -17,26 +27,21 @@ object Level { } def encode(level: Level): tf.bug.fancadescodec.Entry = { - val boundary = level.blocks.foldLeft(fansc.Boundary(0, 0, 0)) { - case (fansc.Boundary(lr, du, nf), block) => - val tlr = block.position.lr + block.definition.width - val tdu = block.position.du + block.definition.height - val tnf = block.position.nf + block.definition.depth - fansc.Boundary(lr.max(tlr), du.max(tdu), nf.max(tnf)) - } + val boundary = level.boundary val objs: Vector[fansc.Obj] = { val initialVector = Vector.fill(boundary.sizeNF, boundary.sizeDU, boundary.sizeLR)(fansc.Obj.empty) level.blocks.foldLeft(initialVector) { case (ew, nb) => val fansc.Position(sx, sy, sz) = nb.position - (0 until nb.definition.depth).foldLeft(ew) { + val nbd = Exists.unwrap(nb.template).definition + (0 until nbd.depth).foldLeft(ew) { case (zw, z) => - (0 until nb.definition.height).foldLeft(zw) { + (0 until nbd.height).foldLeft(zw) { case (yw, y) => - (0 until nb.definition.width).foldLeft(yw) { + (0 until nbd.width).foldLeft(yw) { case (xw, x) => - val objInd = x + (y * nb.definition.width) + (z * nb.definition.width * nb.definition.height) - xw.updated(sz + z, xw(sz + z).updated(sy + y, xw(sz + z)(sy + y).updated(sx + x, nb.definition.ids(objInd)))) + val objInd = x + (y * nbd.width) + (z * nbd.width * nbd.height) + xw.updated(sz + z, xw(sz + z).updated(sy + y, xw(sz + z)(sy + y).updated(sx + x, nbd.ids(objInd)))) } } } diff --git a/graph/src/main/scala/tf/bug/fancadegraph/Main.scala b/graph/src/main/scala/tf/bug/fancadegraph/Main.scala index 93c4d25..3c65799 100644 --- a/graph/src/main/scala/tf/bug/fancadegraph/Main.scala +++ b/graph/src/main/scala/tf/bug/fancadegraph/Main.scala @@ -3,6 +3,7 @@ package tf.bug.fancadegraph import cats.effect.{Blocker, ExitCode, IO, IOApp} import fs2._ import java.nio.file.{Paths, StandardOpenOption} +import polymorphic._ import scalax.collection.Graph import scalax.collection.GraphEdge.DiHyperEdge import scodec.stream.StreamEncoder @@ -11,8 +12,8 @@ import tf.bug.fancadescodec.{Cartridge, Codecs, Position} object Main extends IOApp { override def run(args: List[String]): IO[ExitCode] = { - val winBlock = Block(Position(0, 0, 0), BlockDefinition.Win, true) - val loseBlock = Block(Position(0, 0, 3), BlockDefinition.Lose, true) + val winBlock = Block(Position(0, 0, 0), Exists(Template(BlockDefinition.Win, true))) + val loseBlock = Block(Position(0, 0, 3), Exists(Template(BlockDefinition.Lose, true))) val loseAfter = Pin(loseBlock, BlockDefinition.Lose.after) val winBefore = Pin(winBlock, BlockDefinition.Win.before) val level = Level( diff --git a/graph/src/main/scala/tf/bug/fancadegraph/Pin.scala b/graph/src/main/scala/tf/bug/fancadegraph/Pin.scala index 65a8a68..5a7961d 100644 --- a/graph/src/main/scala/tf/bug/fancadegraph/Pin.scala +++ b/graph/src/main/scala/tf/bug/fancadegraph/Pin.scala @@ -1,6 +1,6 @@ package tf.bug.fancadegraph case class Pin( - owner: Block[_], + owner: Block, definition: PinDefinition ) diff --git a/graph/src/main/scala/tf/bug/fancadegraph/Template.scala b/graph/src/main/scala/tf/bug/fancadegraph/Template.scala new file mode 100644 index 0000000..b8de529 --- /dev/null +++ b/graph/src/main/scala/tf/bug/fancadegraph/Template.scala @@ -0,0 +1,3 @@ +package tf.bug.fancadegraph + +case class Template[T](definition: BlockDefinition[T], args: T)(implicit val ev: Argument[T]) diff --git a/tagless/src/main/scala/tf/bug/fancadetagless/Fancade.scala b/tagless/src/main/scala/tf/bug/fancadetagless/Fancade.scala index 292e977..bc7a49c 100644 --- a/tagless/src/main/scala/tf/bug/fancadetagless/Fancade.scala +++ b/tagless/src/main/scala/tf/bug/fancadetagless/Fancade.scala @@ -1,35 +1,56 @@ package tf.bug.fancadetagless -import tf.bug.fancadegraph.Level -import tf.bug.fancadegraph.BlockDefinition +import cats._ +import cats.implicits._ +import higherkindness.droste._ +import polymorphic._ import tf.bug.fancadegraph.PinDefinition +import tf.bug.fancadegraph.Level import tf.bug.fancadegraph.Block +import tf.bug.fancadegraph.Template import tf.bug.fancadescodec.Position -import scalax.collection.Graph -import tf.bug.fancadegraph.Argument -sealed trait Fancade { +sealed trait Fancade[A] { + val template: Exists[Template] val pins: Vector[PinDefinition] - - def render: Level } object Fancade { - final case class Capture[T](newBlock: BlockDefinition[T], args: T, pins: Vector[PinDefinition])(implicit arg: Argument[T]) extends Fancade { - override def render: Level = { - Level( - Set( - Block( - Position(0, 0, 0), - newBlock, - args - ) - ), - Graph.empty - ) - } + implicit val fancadeFunctor: Functor[Fancade] = new Functor[Fancade] { + override def map[A, B](fa: Fancade[A])(f: A => B): Fancade[B] = + fa match { + case Capture(template, pins) => + Capture(template, pins) + case Use(children, template, inputs, pins) => + Use(children.map(f), template, inputs, pins) + } + } + + case class Capture[A](template: Exists[Template], pins: Vector[PinDefinition]) extends Fancade[A] + case class Use[A](children: Vector[A], template: Exists[Template], inputs: Vector[PinDefinition], pins: Vector[PinDefinition]) extends Fancade[A] + + def flatten: Algebra[Fancade, PointerChain[Fancade]] = Algebra { + case Capture(template, pins) => + PointerChain.single(Capture(template, pins)) + case Use(children, template, inputs, pins) => + ??? + } + val stack: FancadeF => PointerChain[Fancade] = scheme.cata(flatten) + def render(f: FancadeF): Level = { + val pointerChain = stack(f) + val blocks = pointerChain.map { + case (ind, fc) => + Block( + Position(0, ind, 0), + fc.template + ) + } + + Level( + blocks.toSet, + ??? + ) } - final case class Use(inputs: Vector[Fancade]) } diff --git a/tagless/src/main/scala/tf/bug/fancadetagless/Fanscript.scala b/tagless/src/main/scala/tf/bug/fancadetagless/Fanscript.scala index 8a26a2f..d69e286 100644 --- a/tagless/src/main/scala/tf/bug/fancadetagless/Fanscript.scala +++ b/tagless/src/main/scala/tf/bug/fancadetagless/Fanscript.scala @@ -1,6 +1,11 @@ package tf.bug.fancadetagless +import higherkindness.droste.data._ +import polymorphic._ import tf.bug.fancadetagless.Fanscript.{Position, ScreenSize} +import tf.bug.fancadescodec.Metadata.Sample +import tf.bug.fancadegraph.BlockDefinition +import tf.bug.fancadegraph.Template trait Fanscript[F[_]] { @@ -25,7 +30,7 @@ trait Fanscript[F[_]] { def createObj(original: F[Obj]): F[Obj] def destroyObj(obj: F[Obj]): F[Obj] - def playSound(loop: Boolean, sound: Nothing)(volume: F[Float], pitch: F[Float]): F[Float] + def playSound(loop: Boolean, sound: Sample)(volume: F[Float], pitch: F[Float]): F[Float] def stopSound(channel: F[Float]): F[Unit] def volumePitch(channel: F[Float], volume: F[Float], pitch: F[Float]): F[Unit] @@ -70,6 +75,92 @@ trait Fanscript[F[_]] { object Fanscript { + trait Program[R] { + def run[F[_]](implicit interp: Fanscript[F]): F[R] + } + + implicit object fancade extends Fanscript[FancadeW] { + + override def lift(value: Float): FancadeW[Float] = + Fix(Fancade.Capture( + Exists(Template( + BlockDefinition.NumberValue, + value + )), + Vector(BlockDefinition.NumberValue.output) + )) + override def lift(value: Vector3): FancadeW[Vector3] = ??? + override def lift(value: Rotation): FancadeW[Rotation] = ??? + override def lift(value: Boolean): FancadeW[Boolean] = ??? + + override def win(stop: Boolean): FancadeW[Unit] = ??? + override def lose(stop: Boolean): FancadeW[Unit] = ??? + override def setScore(score: FancadeW[Float]): FancadeW[Unit] = ??? + override def setCamera(position: FancadeW[Vector3], rotation: FancadeW[Rotation], distance: FancadeW[Float]): FancadeW[Unit] = ??? + override def setLight(position: FancadeW[Vector3], rotation: FancadeW[Rotation]): FancadeW[Unit] = ??? + override def screenSize: FancadeW[ScreenSize] = ??? + override def accelerometer: FancadeW[Vector3] = ??? + + override def getPosition(obj: FancadeW[Obj]): FancadeW[Position] = + Fix(Fancade.Use( + Vector(obj), + Exists(Template( + BlockDefinition.GetPosition, + () + )), + Vector(BlockDefinition.GetPosition.obj), + Vector(BlockDefinition.GetPosition.position) + )) + override def setPosition(obj: FancadeW[Obj], position: FancadeW[Vector3], rotation: FancadeW[Rotation]): FancadeW[Unit] = ??? + override def raycast(from: FancadeW[Vector3], to: FancadeW[Vector3]): FancadeW[Raycast] = ??? + override def getSize(obj: FancadeW[Obj]): FancadeW[Size] = ??? + override def setVisible(obj: FancadeW[Obj], visible: FancadeW[Boolean]): FancadeW[Unit] = ??? + override def createObj(original: FancadeW[Obj]): FancadeW[Obj] = ??? + override def destroyObj(obj: FancadeW[Obj]): FancadeW[Obj] = ??? + + override def playSound(loop: Boolean, sound: Sample)(volume: FancadeW[Float], pitch: FancadeW[Float]): FancadeW[Float] = ??? + override def stopSound(channel: FancadeW[Float]): FancadeW[Unit] = ??? + override def volumePitch(channel: FancadeW[Float], volume: FancadeW[Float], pitch: FancadeW[Float]): FancadeW[Unit] = ??? + + override def addForce(obj: FancadeW[Obj], force: FancadeW[Vector3], applyAt: FancadeW[Vector3], torque: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def getVelocity(obj: FancadeW[Obj]): FancadeW[Velocity] = ??? + override def setVelocity(obj: FancadeW[Obj], velocity: FancadeW[Vector3], spin: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def setLocked(obj: FancadeW[Obj], position: FancadeW[Vector3], rotation: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def setMass(obj: FancadeW[Obj], mass: FancadeW[Float]): FancadeW[Unit] = ??? + override def setFriction(obj: FancadeW[Obj], friction: FancadeW[Float]): FancadeW[Unit] = ??? + override def setBounciness(obj: FancadeW[Obj], bounciness: FancadeW[Float]): FancadeW[Unit] = ??? + override def setGravity(gravity: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def addConstraint(base: FancadeW[Obj], part: FancadeW[Obj], pivot: FancadeW[Vector3]): FancadeW[Constraint] = ??? + override def linearLimits(constraint: FancadeW[Constraint], lower: FancadeW[Vector3], upper: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def angularLimits(constraint: FancadeW[Constraint], lower: FancadeW[Vector3], upper: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def linearSpring(constraint: FancadeW[Constraint], stiffness: FancadeW[Vector3], damping: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def angularSpring(constraint: FancadeW[Constraint], stiffness: FancadeW[Vector3], damping: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def linearMotor(constraint: FancadeW[Constraint], speed: FancadeW[Vector3], force: FancadeW[Vector3]): FancadeW[Unit] = ??? + override def angularMotor(constraint: FancadeW[Constraint], speed: FancadeW[Vector3], force: FancadeW[Vector3]): FancadeW[Unit] = ??? + + override def ifElse(cond: FancadeW[Boolean])(ifTrue: FancadeW[Unit])(ifFalse: FancadeW[Unit]): FancadeW[Unit] = ??? + override def onPlay(onPlay: FancadeW[Unit]): FancadeW[Unit] = ??? + override def onBoxArt(onBoxArt: FancadeW[Unit]): FancadeW[Unit] = ??? + override def onTouch(onTouch: FancadeW[Touch] => FancadeW[Unit]): FancadeW[Unit] = ??? + + override def screenSizeWidth(screenSize: FancadeW[ScreenSize]): FancadeW[Float] = ??? + override def screenSizeHeight(screenSize: FancadeW[ScreenSize]): FancadeW[Float] = ??? + + override def positionPosition(position: FancadeW[Position]): FancadeW[Vector3] = ??? + override def positionRotation(position: FancadeW[Position]): FancadeW[Rotation] = ??? + + override def raycastHit(raycast: FancadeW[Raycast]): FancadeW[Boolean] = ??? + override def raycastPosition(raycast: FancadeW[Raycast]): FancadeW[Vector3] = ??? + override def raycastObj(raycast: FancadeW[Raycast]): FancadeW[Obj] = ??? + + override def sizeMin(size: FancadeW[Size]): FancadeW[Vector3] = ??? + override def sizeMax(size: FancadeW[Size]): FancadeW[Vector3] = ??? + + override def velocityVelocity(velocity: FancadeW[Velocity]): FancadeW[Vector3] = ??? + override def velocitySpin(velocity: FancadeW[Velocity]): FancadeW[Vector3] = ??? + + } + case class ScreenSize(width: Float, height: Float) case class Position(position: Vector3, rotation: Rotation) case class Raycast(hit: Boolean, position: Vector3, obj: Obj) diff --git a/tagless/src/main/scala/tf/bug/fancadetagless/PointerChain.scala b/tagless/src/main/scala/tf/bug/fancadetagless/PointerChain.scala new file mode 100644 index 0000000..535deb9 --- /dev/null +++ b/tagless/src/main/scala/tf/bug/fancadetagless/PointerChain.scala @@ -0,0 +1,22 @@ +package tf.bug.fancadetagless + +import cats._ +import cats.implicits._ + +case class PointerChain[F[_]](internal: Map[Int, F[Int]])(implicit functor: Functor[F]) { + + def ++(other: PointerChain[F]): PointerChain[F] = { + ??? + } + + def map[B](f: ((Int, F[Int])) => B): Vector[B] = internal.map(f).toVector + +} + +object PointerChain { + + def empty[F[_] : Functor]: PointerChain[F] = PointerChain(Map.empty) + + def single[F[_] : Functor](value: F[Nothing]): PointerChain[F] = PointerChain(Map(0 -> value.widen)) + +} diff --git a/tagless/src/main/scala/tf/bug/fancadetagless/package.scala b/tagless/src/main/scala/tf/bug/fancadetagless/package.scala index 63efb49..851d672 100644 --- a/tagless/src/main/scala/tf/bug/fancadetagless/package.scala +++ b/tagless/src/main/scala/tf/bug/fancadetagless/package.scala @@ -1,7 +1,10 @@ package tf.bug +import higherkindness.droste.data.Fix + package object fancadetagless { - type FancadeW[A] = Fancade + type FancadeF = Fix[Fancade] + type FancadeW[A] = FancadeF } diff --git a/tagless/src/test/scala/tf/bug/fancadetagless/FancadeSuite.scala b/tagless/src/test/scala/tf/bug/fancadetagless/FancadeSuite.scala index b01f6bc..8777153 100644 --- a/tagless/src/test/scala/tf/bug/fancadetagless/FancadeSuite.scala +++ b/tagless/src/test/scala/tf/bug/fancadetagless/FancadeSuite.scala @@ -5,10 +5,51 @@ import tf.bug.fancadegraph.BlockDefinition class FancadeSuite extends munit.FunSuite { test("A capture creates one block") { - val capture = Fancade.Capture[String](BlockDefinition.GetNumber, "hello", Vector( + val capture: FancadeF = Fix(Fancade.Capture[String](BlockDefinition.GetNumber, "hello", Vector( BlockDefinition.GetNumber.output + ))) + assertEquals(Fancade.render(capture).blocks.size, 1) + } + + test("Stacking 1 + 1 results in a 2-element map") { + val one: FancadeF = Fix(Fancade.Capture(BlockDefinition.NumberValue, 1.0f, Vector( + BlockDefinition.NumberValue.output + ))) + val add: FancadeF = Fix(Fancade.Use( + Vector(one, one), + BlockDefinition.AddNumbers, + (), + Vector( + BlockDefinition.AddNumbers.input1, + BlockDefinition.AddNumbers.input2 + ), + Vector( + BlockDefinition.AddNumbers.output + ) + )) + val stacked = Fancade.stack(add) + assertEquals(stacked, Map( + 1 -> Fancade.Capture(BlockDefinition.NumberValue, 1.0f, Vector(BlockDefinition.NumberValue.output)), + 0 -> Fancade.Use(Vector(1, 1), BlockDefinition.AddNumbers, (), + Vector( + BlockDefinition.AddNumbers.input1, + BlockDefinition.AddNumbers.input2 + ), + Vector(BlockDefinition.AddNumbers.output) + ) + )) + } + + test("A constant creates a capture") { + val program = new Fanscript.Program[Float] { + override def run[F[_]](implicit interp: Fanscript[F]): F[Float] = interp.lift(1.0f) + } + val result = program.run[FancadeW] + assertEquals(result, Fancade.Capture( + BlockDefinition.NumberValue, + 1.0f, + Vector(BlockDefinition.NumberValue.output) )) - assertEquals(capture.render.blocks.size, 1) } }