Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

514 lines
15 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package wow.doge.mygame
  2. import com.jme3.app.SimpleApplication
  3. import com.jme3.app.state.AppStateManager
  4. import scala.reflect.ClassTag
  5. import com.jme3.app.state.AppState
  6. import com.jme3.scene.Node
  7. import com.jme3.scene.Spatial
  8. import com.simsilica.es.EntityData
  9. import com.simsilica.es.EntityComponent
  10. import com.simsilica.es.EntityId
  11. import akka.actor.typed.ActorRef
  12. import akka.util.Timeout
  13. import akka.actor.typed.Scheduler
  14. import monix.bio.Task
  15. import com.jme3.input.InputManager
  16. import com.jme3.input.controls.Trigger
  17. import com.jme3.input.controls.InputListener
  18. import com.jme3.math.Vector3f
  19. import wow.doge.mygame.math.ImVector3f
  20. import com.jme3.scene.Geometry
  21. import wow.doge.mygame.state.CardinalDirection
  22. import wow.doge.mygame.state.CanMove
  23. import com.jme3.renderer.Camera
  24. import scala.jdk.CollectionConverters._
  25. import wow.doge.mygame.utils.JFXConsoleStreamable
  26. import com.jme3.app.Application
  27. import java.util.concurrent.Callable
  28. import scala.concurrent.Future
  29. import scala.concurrent.ExecutionContext
  30. import com.jme3.scene.SceneGraphVisitor
  31. import monix.reactive.Observable
  32. import com.jme3.asset.AssetManager
  33. import com.jme3.asset.AssetLocator
  34. import com.jme3.input.controls.ActionListener
  35. import monix.reactive.OverflowStrategy
  36. import monix.execution.Ack
  37. import monix.execution.Cancelable
  38. import monix.execution.cancelables.SingleAssignCancelable
  39. import com.jme3.input.Action
  40. import com.jme3.bullet.PhysicsSpace
  41. import com.jme3.bullet.collision.PhysicsCollisionListener
  42. import com.jme3.bullet.collision.PhysicsCollisionEvent
  43. import com.jme3.bullet.PhysicsTickListener
  44. import monix.reactive.observers.Subscriber
  45. import monix.execution.Ack.Continue
  46. import monix.execution.Ack.Stop
  47. case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
  48. case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float)
  49. package object implicits {
  50. type PrePhysicsTickEvent = PhysicsTickEvent
  51. type PhysicsTickObservable =
  52. Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
  53. implicit class JMEAppExt(val app: Application) extends AnyVal {
  54. /**
  55. * Blocking task. Execute on a thread pool meant for blocking operations.
  56. * Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead.
  57. *
  58. * @param cb
  59. * @param ec
  60. * @return
  61. */
  62. def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] =
  63. Future {
  64. app
  65. .enqueue(new Callable[T]() {
  66. override def call(): T = cb()
  67. })
  68. .get()
  69. }
  70. /**
  71. * Blocking task. Execute on a thread pool meant for blocking operations.
  72. * Same as enqueue, but returns a Monix Task instead of Future
  73. * @param cb
  74. * @param ec
  75. * @return
  76. */
  77. def enqueueL[T](cb: () => T): Task[T] =
  78. Task
  79. .deferFutureAction(implicit s => enqueueF(cb))
  80. def enqueueF[T](cb: => T) =
  81. app.enqueue(new Runnable {
  82. override def run() = cb
  83. })
  84. def enqueueT(cb: => Unit) =
  85. Task(enqueueF(cb))
  86. }
  87. implicit class StateManagerExt(val sm: AppStateManager) extends AnyVal {
  88. def state[S <: AppState]()(implicit c: ClassTag[S]): S =
  89. sm.getState(c.runtimeClass.asInstanceOf[Class[S]])
  90. }
  91. implicit class SimpleApplicationExt(val sa: SimpleApplication)
  92. extends AnyVal {
  93. def stateManager: AppStateManager = sa.getStateManager()
  94. def inputManager: InputManager = sa.getInputManager()
  95. def assetManager: AssetManager = sa.getAssetManager()
  96. def guiNode = sa.getGuiNode()
  97. def flyCam = Option(sa.getFlyByCamera())
  98. def camera = sa.getCamera()
  99. def viewPort = sa.getViewPort()
  100. }
  101. implicit class NodeExt(val n: Node) extends AnyVal {
  102. /**
  103. * Attaches the given child
  104. *
  105. * @param s
  106. * @return
  107. */
  108. def child(s: Spatial): Node = {
  109. n.attachChild(s)
  110. n
  111. }
  112. /**
  113. * Gets the list of children as a scala collection
  114. *
  115. * @return
  116. */
  117. // def children = n.getChildren().asScala.toSeq
  118. def children = Observable.fromIterable(n.getChildren().asScala)
  119. /**
  120. * Attach given children
  121. *
  122. * @param lst
  123. */
  124. def children(lst: Iterable[Spatial]): Unit = {
  125. for (c <- lst) n.child(c)
  126. }
  127. def depthFirst(cb: Spatial => Unit) =
  128. n.depthFirstTraversal(new SceneGraphVisitor() {
  129. override def visit(s: Spatial) = cb(s)
  130. })
  131. def observableDepthFirst(): Observable[Spatial] = {
  132. def loop(
  133. subscriber: Subscriber[Spatial],
  134. spatial: Spatial
  135. ): Task[Unit] = {
  136. //spatial can be either a node or a geometry, but it's not a sealed trait
  137. spatial match {
  138. case node: Node =>
  139. Task.deferFuture(subscriber.onNext(node)).flatMap {
  140. case Ack.Continue => {
  141. //modifying a node's children list is forbidden
  142. val children = node.getChildren().asScala.to(LazyList)
  143. if (!children.isEmpty) {
  144. Task.sequence(
  145. children.map(c => loop(subscriber, c))
  146. ) >> Task.unit
  147. } else {
  148. Task.unit
  149. }
  150. }
  151. case Ack.Stop => Task.unit
  152. }
  153. //geomtries do not/cannot have children
  154. case g: Geometry =>
  155. Task.deferFuture(subscriber.onNext(g)) >> Task.unit
  156. case _ => Task.unit
  157. }
  158. }
  159. Observable.create(OverflowStrategy.Unbounded) { sub =>
  160. implicit val sched = sub.scheduler
  161. loop(sub, n).runToFuture
  162. }
  163. }
  164. def breadthFirst(cb: Spatial => Unit) =
  165. n.breadthFirstTraversal(new SceneGraphVisitor() {
  166. override def visit(s: Spatial) = cb(s)
  167. })
  168. def observableBreadthFirst(): Observable[Spatial] = {
  169. def loop(
  170. subscriber: Subscriber[Spatial],
  171. spatials: LazyList[Spatial]
  172. ): Task[Unit] = {
  173. // spatial can be either a node or a geometry, but it's not a sealed trait
  174. spatials match {
  175. case head #:: tail =>
  176. head match {
  177. case g: Geometry =>
  178. Task.deferFuture(subscriber.onNext(g)).flatMap {
  179. case Continue =>
  180. loop(subscriber, tail)
  181. case Stop => Task.unit
  182. }
  183. case node: Node =>
  184. val children = node.getChildren().asScala.to(LazyList)
  185. Task.deferFuture(subscriber.onNext(node)).flatMap {
  186. case Continue =>
  187. loop(subscriber, tail #::: children)
  188. case Stop => Task.unit
  189. }
  190. // case _ => loop(subscriber, tail)
  191. }
  192. case LazyList() => Task.unit
  193. }
  194. }
  195. Observable.create(OverflowStrategy.Unbounded) { sub =>
  196. implicit val sched = sub.scheduler
  197. loop(sub, LazyList(n)).runToFuture
  198. }
  199. }
  200. }
  201. implicit class EntityDataExt(val ed: EntityData) extends AnyVal {
  202. def query = new EntityQuery(ed)
  203. // def entities[T <: EntityComponent](entities: Seq[T])
  204. }
  205. implicit class EntityExt(val e: EntityId) extends AnyVal {
  206. def withComponents(classes: EntityComponent*)(implicit
  207. ed: EntityData
  208. ): EntityId = {
  209. ed.setComponents(e, classes: _*)
  210. e
  211. }
  212. }
  213. implicit class ActorRefExt[Req](val a: ActorRef[Req]) extends AnyVal {
  214. import akka.actor.typed.scaladsl.AskPattern._
  215. def askT[Res](
  216. replyTo: ActorRef[Res] => Req
  217. )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
  218. Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
  219. }
  220. }
  221. // def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
  222. // ask(replyTo)(timeout, scheduler)
  223. // }
  224. implicit class InputManagerExt(val inputManager: InputManager)
  225. extends AnyVal {
  226. def withMapping(mapping: String, triggers: Trigger*): InputManager = {
  227. inputManager.addMapping(mapping, triggers: _*)
  228. inputManager
  229. }
  230. def withListener(listener: InputListener, mappings: String*) = {
  231. inputManager.addListener(listener, mappings: _*)
  232. inputManager
  233. }
  234. def observableAction(mappingNames: String*): Observable[ActionEvent] = {
  235. Observable.create(OverflowStrategy.Unbounded) { sub =>
  236. val c = SingleAssignCancelable()
  237. val al = new ActionListener {
  238. override def onAction(
  239. binding: String,
  240. value: Boolean,
  241. tpf: Float
  242. ): Unit = {
  243. if (
  244. sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
  245. )
  246. c.cancel()
  247. }
  248. }
  249. inputManager.addListener(al, mappingNames: _*)
  250. c := Cancelable(() => inputManager.removeListener(al))
  251. c
  252. }
  253. }
  254. }
  255. implicit class PhysicsSpaceExt(val space: PhysicsSpace) extends AnyVal {
  256. def collisionObservable(): Observable[PhysicsCollisionEvent] = {
  257. Observable.create(OverflowStrategy.Unbounded) { sub =>
  258. val c = SingleAssignCancelable()
  259. val cl = new PhysicsCollisionListener {
  260. override def collision(event: PhysicsCollisionEvent): Unit = {
  261. if (sub.onNext(event) == Ack.Stop)
  262. c.cancel()
  263. }
  264. }
  265. space.addCollisionListener(cl)
  266. c := Cancelable(() => space.removeCollisionListener(cl))
  267. c
  268. }
  269. }
  270. def physicsTickObservable(): PhysicsTickObservable = {
  271. Observable.create(OverflowStrategy.Unbounded) { sub =>
  272. val c = SingleAssignCancelable()
  273. val cl = new PhysicsTickListener {
  274. override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
  275. val event = PhysicsTickEvent(space, tpf)
  276. if (sub.onNext(Left(event)) == Ack.Stop)
  277. c.cancel()
  278. }
  279. override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
  280. val event = PhysicsTickEvent(space, tpf)
  281. if (sub.onNext(Right(event)) == Ack.Stop)
  282. c.cancel()
  283. }
  284. }
  285. space.addTickListener(cl)
  286. c := Cancelable(() => space.removeTickListener(cl))
  287. c
  288. }
  289. }
  290. }
  291. implicit class AssetManagerExt(val am: AssetManager) extends AnyVal {
  292. def registerLocator(
  293. assetPath: os.RelPath,
  294. locator: Class[_ <: AssetLocator]
  295. ): Unit = {
  296. am.registerLocator(assetPath.toString(), locator)
  297. }
  298. def loadModel(assetPath: os.RelPath): Spatial = {
  299. am.loadModel(assetPath.toString())
  300. }
  301. }
  302. implicit class Vector3fExt(val v: Vector3f) extends AnyVal {
  303. def +=(that: Vector3f) = v.addLocal(that)
  304. def *=(that: Vector3f) = v.multLocal(that)
  305. def -=(that: Vector3f) = v.subtractLocal(that)
  306. def /=(that: Vector3f) = v.divideLocal(that)
  307. def unary_- = v.negateLocal()
  308. def immutable = ImVector3f(v.x, v.y, v.z)
  309. }
  310. implicit class ImVector3fExt(val v: ImVector3f) extends AnyVal {
  311. def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
  312. def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
  313. def *(f: Float): ImVector3f =
  314. // v.copy(v.x * f, v.y * f, v.x * f)
  315. v * ImVector3f(f, f, f)
  316. def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z)
  317. def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z)
  318. def unary_- = v.copy(-v.x, -v.y, -v.z)
  319. // def unary_-(that: ImVector3f) = this.copy(this.x, this.y, this.z)
  320. def mutable = new Vector3f(v.x, v.y, v.z)
  321. }
  322. // implicit val implVector3fForVector3 = new Vector3[Vector3f] {
  323. // override def +(implicit v: Vector3f, that: com.jme3.math.Vector3f): Unit =
  324. // v += that
  325. // override def *(implicit v: Vector3f, that: Vector3f): Unit = v *= that
  326. // override def -(implicit v: Vector3f, that: Vector3f): Unit = v -= that
  327. // override def /(implicit v: Vector3f, that: Vector3f): Unit = v /= that
  328. // }
  329. // implicit val implImVector3fForVector3 = new Vector3[ImVector3f] {
  330. // override def +(implicit v: ImVector3f, that: ImVector3f): Unit =
  331. // v + that
  332. // override def *(implicit v: ImVector3f, that: ImVector3f): Unit = v * that
  333. // override def -(implicit v: ImVector3f, that: ImVector3f): Unit = v - that
  334. // override def /(implicit v: ImVector3f, that: ImVector3f): Unit = v / that
  335. // }
  336. // def test[T](v: T)(implicit ev: Vector3[T]) = {
  337. // import ev._
  338. // ev.+
  339. // }
  340. implicit val implCanMoveForGeom = new CanMove[Spatial] {
  341. override def move(inst: Spatial, direction: ImVector3f): Unit = {
  342. // val v = inst.getLocalTranslation()
  343. // inst match {
  344. // case n: Node => println(n.getChildren())
  345. // case _ =>
  346. // }
  347. inst.move(direction.mutable)
  348. }
  349. override def getDirection(
  350. cam: Camera,
  351. cardinalDir: CardinalDirection
  352. ): ImVector3f = {
  353. // val camDir =
  354. // cam.getDirection().immutable * 0.6f
  355. // val camLeft = cam.getLeft().immutable * 0.4f
  356. // val zero = ImVector3f.ZERO
  357. // val dir = cardinalDir
  358. // val walkDir = {
  359. // val mutWalkDir = new Vector3f()
  360. // if (dir.left) {
  361. // // ctx.log.trace("left")
  362. // mutWalkDir += (zero + camLeft).mutable
  363. // }
  364. // if (dir.right) {
  365. // // ctx.log.trace("right")
  366. // mutWalkDir += (zero + -camLeft).mutable
  367. // }
  368. // if (dir.up) {
  369. // // ctx.log.trace("up")
  370. // mutWalkDir += (zero + camDir).mutable
  371. // }
  372. // if (dir.down) {
  373. // // ctx.log.trace("down")
  374. // mutWalkDir += (zero + -camDir).mutable
  375. // }
  376. // mutWalkDir.immutable
  377. // }
  378. // walkDir
  379. // val camDir =
  380. // cam.getDirection().immutable * 0.6f
  381. // val camLeft = cam.getLeft().immutable * 0.4f
  382. val zero = ImVector3f.ZERO
  383. val dir = cardinalDir
  384. val walkDir = {
  385. val mutWalkDir = new Vector3f()
  386. if (dir.left) {
  387. // ctx.log.trace("left")
  388. mutWalkDir += (zero + ImVector3f(-1, 0, 0)).mutable
  389. }
  390. if (dir.right) {
  391. // ctx.log.trace("right")
  392. mutWalkDir += (zero + ImVector3f(1, 0, 0)).mutable
  393. }
  394. if (dir.up) {
  395. // ctx.log.trace("up")
  396. mutWalkDir += (zero + ImVector3f(0, 0, -1)).mutable
  397. }
  398. if (dir.down) {
  399. // ctx.log.trace("down")
  400. mutWalkDir += (zero + ImVector3f(0, 0, 1)).mutable
  401. }
  402. mutWalkDir.immutable
  403. }
  404. walkDir
  405. }
  406. }
  407. implicit val implJFXConsoleStreamForTextArea =
  408. new JFXConsoleStreamable[scalafx.scene.control.TextArea] {
  409. override def println(
  410. ta: scalafx.scene.control.TextArea,
  411. text: String
  412. ): Unit =
  413. ta.appendText(text + "\n")
  414. override def print(
  415. ta: scalafx.scene.control.TextArea,
  416. text: String
  417. ): Unit =
  418. ta.appendText(text)
  419. }
  420. // val TasktoUIO = new FunctionK[Task, UIO] {
  421. // def apply[T](f: Task[T]): UIO[T] =
  422. // f.hideErrors
  423. // }
  424. }
  425. // Observable.create(OverflowStrategy.Unbounded) { sub =>
  426. // // val c = SingleAssignCancelable()
  427. // val visitor = new SceneGraphVisitor {
  428. // override def visit(s: Spatial): Unit = {
  429. // sub.onNext(s)
  430. // // if (sub.onNext(s) == Ack.Stop)
  431. // // c.cancel()
  432. // }
  433. // }
  434. // n.depthFirstTraversal(visitor)
  435. // // c := Cancelable(() => ???)
  436. // // c
  437. // Cancelable.empty