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.

604 lines
18 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
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.subsystems.movement.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 com.jme3.scene.SceneGraphVisitor
  28. import monix.reactive.Observable
  29. import com.jme3.asset.AssetManager
  30. import com.jme3.asset.AssetLocator
  31. import com.jme3.input.controls.ActionListener
  32. import monix.reactive.OverflowStrategy
  33. import monix.execution.Ack
  34. import monix.execution.Cancelable
  35. import monix.execution.cancelables.SingleAssignCancelable
  36. import com.jme3.input.Action
  37. import com.jme3.bullet.PhysicsSpace
  38. import com.jme3.bullet.collision.PhysicsCollisionListener
  39. import com.jme3.bullet.collision.PhysicsCollisionEvent
  40. import com.jme3.bullet.PhysicsTickListener
  41. import monix.reactive.observers.Subscriber
  42. import monix.execution.Ack.Continue
  43. import monix.execution.Ack.Stop
  44. import com.jme3.bullet.BulletAppState
  45. import wow.doge.mygame.state.MyBaseState
  46. import monix.bio.UIO
  47. import com.jme3.bullet.control.BetterCharacterControl
  48. import com.jme3.scene.control.AbstractControl
  49. import com.jme3.scene.CameraNode
  50. import com.jme3.scene.control.CameraControl.ControlDirection
  51. import com.jme3.bullet.control.AbstractPhysicsControl
  52. import com.jme3.scene.control.Control
  53. import com.typesafe.scalalogging.Logger
  54. import com.typesafe.scalalogging.LazyLogging
  55. import com.jme3.input.controls.AnalogListener
  56. import com.jme3.math.Quaternion
  57. import com.jme3.math.FastMath
  58. import wow.doge.mygame.subsystems.movement.RotateDir
  59. case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
  60. case class AnalogEvent(binding: Action, value: Float, tpf: Float)
  61. case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float)
  62. package object implicits {
  63. type PrePhysicsTickEvent = PhysicsTickEvent
  64. type PhysicsTickObservable =
  65. Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
  66. implicit class JMEAppExt(private val app: Application) extends AnyVal {
  67. // /**
  68. // * Blocking task. Execute on a thread pool meant for blocking operations.
  69. // * Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead.
  70. // *
  71. // * @param cb
  72. // * @param ec
  73. // * @return
  74. // */
  75. // def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] =
  76. // Future {
  77. // app
  78. // .enqueue(new Callable[T]() {
  79. // override def call(): T = cb()
  80. // })
  81. // .get()
  82. // }
  83. // /**
  84. // * Blocking task. Execute on a thread pool meant for blocking operations.
  85. // * Same as enqueue, but returns a Monix Task instead of Future
  86. // * @param cb
  87. // * @param ec
  88. // * @return
  89. // */
  90. // def enqueueL[T](cb: () => T): Task[T] =
  91. // Task
  92. // .deferFutureAction(implicit s => enqueueF(cb))
  93. def enqueueF[T](cb: => T) =
  94. app.enqueue(new Runnable {
  95. override def run() = cb
  96. })
  97. def enqueueT(cb: => Unit) =
  98. Task(enqueueF(cb))
  99. }
  100. implicit class StateManagerExt(private val sm: AppStateManager)
  101. extends AnyVal {
  102. def state[S <: AppState]()(implicit c: ClassTag[S]): S =
  103. sm.getState(c.runtimeClass.asInstanceOf[Class[S]])
  104. }
  105. implicit class SimpleApplicationExt[T <: SimpleApplication](private val sa: T)
  106. extends AnyVal {
  107. def stateManager: AppStateManager = sa.getStateManager()
  108. def inputManager: InputManager = sa.getInputManager()
  109. def assetManager: AssetManager = sa.getAssetManager()
  110. def guiNode = sa.getGuiNode()
  111. def flyCam = Option(sa.getFlyByCamera())
  112. def camera = sa.getCamera()
  113. def viewPort = sa.getViewPort()
  114. def rootNode = sa.getRootNode()
  115. def observableTick: Observable[Float] =
  116. Observable.create(OverflowStrategy.Unbounded) { sub =>
  117. val c = SingleAssignCancelable()
  118. val as = new MyBaseState {
  119. override def init(): Unit = {}
  120. override def update(tpf: Float) = {
  121. if (sub.onNext(tpf) == Ack.Stop)
  122. c.cancel()
  123. }
  124. override def stop(): Unit = {}
  125. override def onEnable() = {}
  126. override def onDisable() = {}
  127. }
  128. sa.stateManager.attach(as)
  129. c := Cancelable(() => sa.stateManager.detach(as))
  130. c
  131. }
  132. }
  133. implicit class NodeExt[T <: Node](private val n: T) extends AnyVal {
  134. /**
  135. * Attaches the given child
  136. *
  137. * @param s
  138. * @return
  139. */
  140. def withChild(s: Spatial): Node = {
  141. n.attachChild(s)
  142. n
  143. }
  144. /**
  145. * Gets the list of children as a monix observable
  146. *
  147. * @return
  148. */
  149. // def children = n.getChildren().asScala.toSeq
  150. def observableChildren =
  151. Observable.fromIterable(n.getChildren().asScala)
  152. def children = LazyList.from(n.getChildren().asScala)
  153. /**
  154. * Attach given children
  155. *
  156. * @param lst
  157. */
  158. def withChildren(lst: Spatial*): Node = {
  159. for (c <- lst) n.withChild(c)
  160. n
  161. }
  162. def +=(spatial: Spatial) = n.attachChild(spatial)
  163. def depthFirst(cb: Spatial => Unit) =
  164. n.depthFirstTraversal(new SceneGraphVisitor() {
  165. override def visit(s: Spatial) = cb(s)
  166. })
  167. def observableDepthFirst(): Observable[Spatial] = {
  168. def loop(
  169. subscriber: Subscriber[Spatial],
  170. spatial: Spatial
  171. ): Task[Unit] = {
  172. //spatial can be either a node or a geometry, but it's not a sealed trait
  173. spatial match {
  174. case node: Node =>
  175. Task.deferFuture(subscriber.onNext(node)).flatMap {
  176. case Ack.Continue => {
  177. //modifying a node's children list is forbidden
  178. val children = node.children
  179. if (!children.isEmpty) {
  180. Task.sequence(
  181. children.map(c => loop(subscriber, c))
  182. ) >> Task.unit
  183. } else {
  184. Task.unit
  185. }
  186. }
  187. case Ack.Stop => Task.unit
  188. }
  189. //geomtries do not/cannot have children
  190. case g: Geometry =>
  191. Task.deferFuture(subscriber.onNext(g)) >> Task.unit
  192. case _ => Task.unit
  193. }
  194. }
  195. Observable.create(OverflowStrategy.Unbounded) { sub =>
  196. implicit val sched = sub.scheduler
  197. loop(sub, n).runToFuture
  198. }
  199. }
  200. def breadthFirst(cb: Spatial => Unit) =
  201. n.breadthFirstTraversal(new SceneGraphVisitor() {
  202. override def visit(s: Spatial) = cb(s)
  203. })
  204. def observableBreadthFirst(): Observable[Spatial] = {
  205. def loop(
  206. subscriber: Subscriber[Spatial],
  207. spatials: LazyList[Spatial]
  208. ): Task[Unit] =
  209. spatials match {
  210. // spatial can be either a node or a geometry, but it's not a sealed trait
  211. case head #:: tail =>
  212. head match {
  213. case g: Geometry =>
  214. Task.deferFuture(subscriber.onNext(g)).flatMap {
  215. case Continue =>
  216. loop(subscriber, tail)
  217. case Stop => Task.unit
  218. }
  219. case node: Node =>
  220. val children = node.children
  221. Task.deferFuture(subscriber.onNext(node)).flatMap {
  222. case Continue =>
  223. loop(subscriber, tail #::: children)
  224. case Stop => Task.unit
  225. }
  226. // case _ => loop(subscriber, tail)
  227. }
  228. case LazyList() => Task.unit
  229. }
  230. Observable.create(OverflowStrategy.Unbounded) { sub =>
  231. implicit val sched = sub.scheduler
  232. loop(sub, LazyList(n)).runToFuture
  233. }
  234. }
  235. def withControl[C <: Control](ctrl: C) = {
  236. n.addControl(ctrl)
  237. n
  238. }
  239. def withLocalTranslation(dir: ImVector3f) = {
  240. n.setLocalTranslation(dir.mutable)
  241. n
  242. }
  243. def withRotate(xAngle: Float, yAngle: Float, zAngle: Float) = {
  244. n.rotate(xAngle, yAngle, zAngle)
  245. n
  246. }
  247. def localRotation = n.getLocalRotation()
  248. def localTranslation = n.getLocalTranslation()
  249. }
  250. implicit class CameraNodeExt(private val cn: CameraNode) {
  251. def withControlDir(controlDir: ControlDirection) = {
  252. cn.setControlDir(controlDir)
  253. cn
  254. }
  255. def withLookAt(position: ImVector3f, upVector: ImVector3f) = {
  256. cn.lookAt(position.mutable, upVector.mutable)
  257. cn
  258. }
  259. }
  260. implicit class EntityDataExt(private val ed: EntityData) extends AnyVal {
  261. def query = new EntityQuery(ed)
  262. // def entities[T <: EntityComponent](entities: Seq[T])
  263. }
  264. implicit class EntityExt(private val e: EntityId) extends AnyVal {
  265. def withComponents(classes: EntityComponent*)(implicit
  266. ed: EntityData
  267. ): EntityId = {
  268. ed.setComponents(e, classes: _*)
  269. e
  270. }
  271. }
  272. implicit class ActorRefExt[Req](private val a: ActorRef[Req]) extends AnyVal {
  273. import akka.actor.typed.scaladsl.AskPattern._
  274. /**
  275. * @param replyTo
  276. * @param timeout
  277. * @param scheduler
  278. * @return
  279. */
  280. def askL[Res](
  281. replyTo: ActorRef[Res] => Req
  282. )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
  283. Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
  284. }
  285. def ??[Res](
  286. replyTo: ActorRef[Res] => Req
  287. )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
  288. askL(replyTo)
  289. /**
  290. * Same as [[tell]], but wrapped in a Task
  291. *
  292. * @param msg
  293. * @return
  294. */
  295. def tellL(msg: Req) = UIO(a.tell(msg))
  296. def !!(msg: Req) = tellL(msg)
  297. }
  298. // def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
  299. // ask(replyTo)(timeout, scheduler)
  300. // }
  301. implicit class InputManagerExt(private val inputManager: InputManager)
  302. extends AnyVal {
  303. def withMapping(mapping: String, triggers: Trigger*): InputManager = {
  304. inputManager.addMapping(mapping, triggers: _*)
  305. inputManager
  306. }
  307. def withListener(listener: InputListener, mappings: String*) = {
  308. inputManager.addListener(listener, mappings: _*)
  309. inputManager
  310. }
  311. def observableAction(mappingNames: String*): Observable[ActionEvent] = {
  312. Observable.create(OverflowStrategy.DropOld(10)) { sub =>
  313. val c = SingleAssignCancelable()
  314. val al = new ActionListener {
  315. override def onAction(
  316. binding: String,
  317. value: Boolean,
  318. tpf: Float
  319. ): Unit = {
  320. if (
  321. sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
  322. )
  323. c.cancel()
  324. }
  325. }
  326. inputManager.addListener(al, mappingNames: _*)
  327. c := Cancelable(() => inputManager.removeListener(al))
  328. c
  329. }
  330. }
  331. def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
  332. Observable.create(OverflowStrategy.DropOld(100)) { sub =>
  333. val c = SingleAssignCancelable()
  334. val al = new AnalogListener {
  335. override def onAnalog(
  336. binding: String,
  337. value: Float,
  338. tpf: Float
  339. ): Unit = {
  340. if (
  341. sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
  342. )
  343. c.cancel()
  344. }
  345. }
  346. inputManager.addListener(al, mappingNames: _*)
  347. c := Cancelable(() => inputManager.removeListener(al))
  348. c
  349. }
  350. }
  351. }
  352. implicit class PhysicsSpaceExt(private val space: PhysicsSpace)
  353. extends AnyVal {
  354. def collisionObservable(): Observable[PhysicsCollisionEvent] = {
  355. Observable.create(OverflowStrategy.Unbounded) { sub =>
  356. val c = SingleAssignCancelable()
  357. val cl = new PhysicsCollisionListener {
  358. override def collision(event: PhysicsCollisionEvent): Unit = {
  359. if (sub.onNext(event) == Ack.Stop)
  360. c.cancel()
  361. }
  362. }
  363. space.addCollisionListener(cl)
  364. c := Cancelable(() => space.removeCollisionListener(cl))
  365. c
  366. }
  367. }
  368. def physicsTickObservable(): PhysicsTickObservable = {
  369. Observable.create(OverflowStrategy.Unbounded) { sub =>
  370. val c = SingleAssignCancelable()
  371. val cl = new PhysicsTickListener {
  372. override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
  373. val event = PhysicsTickEvent(space, tpf)
  374. if (sub.onNext(Left(event)) == Ack.Stop)
  375. c.cancel()
  376. }
  377. override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
  378. val event = PhysicsTickEvent(space, tpf)
  379. if (sub.onNext(Right(event)) == Ack.Stop)
  380. c.cancel()
  381. }
  382. }
  383. space.addTickListener(cl)
  384. c := Cancelable(() => space.removeTickListener(cl))
  385. c
  386. }
  387. }
  388. //TODO Create a typeclass for this
  389. def +=(anyObject: Any) = space.add(anyObject)
  390. def +=(spatial: Spatial) = space.addAll(spatial)
  391. }
  392. implicit class AssetManagerExt(private val am: AssetManager) extends AnyVal {
  393. def registerLocator(
  394. assetPath: os.RelPath,
  395. locator: Class[_ <: AssetLocator]
  396. ): Unit = {
  397. am.registerLocator(assetPath.toString(), locator)
  398. }
  399. def loadModel(assetPath: os.RelPath): Spatial = {
  400. am.loadModel(assetPath.toString())
  401. }
  402. }
  403. implicit class BulletAppStateExt(private val bas: BulletAppState)
  404. extends AnyVal {
  405. def physicsSpace = bas.getPhysicsSpace()
  406. def speed = bas.getSpeed()
  407. }
  408. implicit class BetterCharacterControlExt(
  409. private val bcc: BetterCharacterControl
  410. ) {
  411. def withJumpForce(force: ImVector3f) = {
  412. bcc.setJumpForce(force.mutable)
  413. bcc
  414. }
  415. }
  416. implicit class Vector3fExt(private val v: Vector3f) extends AnyVal {
  417. //TODO add more operations
  418. def +=(that: Vector3f) = v.addLocal(that)
  419. def +=(that: ImVector3f) = v.addLocal(that.x, that.y, that.z)
  420. def +=:(that: ImVector3f) = v += that
  421. def *=(that: Vector3f) = v.multLocal(that)
  422. def -=(that: Vector3f) = v.subtractLocal(that)
  423. def /=(that: Vector3f) = v.divideLocal(that)
  424. def unary_- = v.negateLocal()
  425. def immutable = ImVector3f(v.x, v.y, v.z)
  426. }
  427. implicit class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
  428. def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
  429. def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
  430. def *(f: Float): ImVector3f =
  431. v.copy(v.x * f, v.y * f, v.z * f)
  432. // v * ImVector3f(f, f, f)
  433. def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z)
  434. def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z)
  435. def unary_- = v.copy(-v.x, -v.y, -v.z)
  436. // def unary_-(that: ImVector3f) = this.copy(this.x, this.y, this.z)
  437. def mutable = new Vector3f(v.x, v.y, v.z)
  438. }
  439. implicit val implCanMoveForBetterCharacterControl =
  440. new CanMove[BetterCharacterControl] {
  441. override def move(
  442. inst: BetterCharacterControl,
  443. direction: ImVector3f
  444. ): Unit = {
  445. // val dir = direction.mutable
  446. // inst.setViewDirection(dir)
  447. // inst.setViewDirection(direction.mutable)
  448. inst.setWalkDirection(direction.mutable.multLocal(50f))
  449. }
  450. override def jump(inst: BetterCharacterControl): Unit = inst.jump()
  451. override def rotate(
  452. inst: BetterCharacterControl,
  453. rotateDir: RotateDir
  454. ): Unit = {
  455. val q =
  456. rotateDir match {
  457. case RotateDir.Left =>
  458. new Quaternion()
  459. .fromAngleAxis(-10f * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
  460. case RotateDir.Right =>
  461. new Quaternion()
  462. .fromAngleAxis(10 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
  463. }
  464. val tmp = new Vector3f()
  465. inst.getViewDirection(tmp)
  466. inst.setViewDirection(q.mult(tmp))
  467. }
  468. override def stop(inst: BetterCharacterControl) =
  469. inst.setWalkDirection(Vector3f.ZERO)
  470. }
  471. implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging {
  472. override def move(inst: Spatial, direction: ImVector3f): Unit = {
  473. inst.move(direction.mutable)
  474. }
  475. override def jump(inst: Spatial): Unit =
  476. logger.warn("`Jump` is not implemented for type `Spatial`")
  477. override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = {
  478. rotateDir match {
  479. case RotateDir.Left => inst.rotate(0, -0.01f, 0)
  480. case RotateDir.Right => inst.rotate(0, 0.01f, 0)
  481. }
  482. }
  483. override def stop(inst: Spatial) = {}
  484. }
  485. implicit val implJFXConsoleStreamForTextArea =
  486. new JFXConsoleStreamable[scalafx.scene.control.TextArea] {
  487. override def println(
  488. ta: scalafx.scene.control.TextArea,
  489. text: String
  490. ): Unit =
  491. ta.appendText(text + "\n")
  492. override def print(
  493. ta: scalafx.scene.control.TextArea,
  494. text: String
  495. ): Unit =
  496. ta.appendText(text)
  497. }
  498. // val TasktoUIO = new FunctionK[Task, UIO] {
  499. // def apply[T](f: Task[T]): UIO[T] =
  500. // f.hideErrors
  501. // }
  502. }
  503. // Observable.create(OverflowStrategy.Unbounded) { sub =>
  504. // // val c = SingleAssignCancelable()
  505. // val visitor = new SceneGraphVisitor {
  506. // override def visit(s: Spatial): Unit = {
  507. // sub.onNext(s)
  508. // // if (sub.onNext(s) == Ack.Stop)
  509. // // c.cancel()
  510. // }
  511. // }
  512. // n.depthFirstTraversal(visitor)
  513. // // c := Cancelable(() => ???)
  514. // // c
  515. // Cancelable.empty