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.

861 lines
24 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
3 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
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
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 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
3 years ago
4 years ago
  1. package wow.doge.mygame
  2. import scala.jdk.CollectionConverters._
  3. import scala.reflect.ClassTag
  4. import akka.actor.typed.ActorRef
  5. import akka.actor.typed.Scheduler
  6. import akka.util.Timeout
  7. import com.jayfella.jme.jfx.JavaFxUI
  8. import com.jme3.app.Application
  9. import com.jme3.app.SimpleApplication
  10. import com.jme3.app.state.AppState
  11. import com.jme3.app.state.AppStateManager
  12. import com.jme3.asset.AssetLocator
  13. import com.jme3.asset.AssetManager
  14. import com.jme3.bullet.BulletAppState
  15. import com.jme3.bullet.PhysicsSpace
  16. import com.jme3.bullet.PhysicsTickListener
  17. import com.jme3.bullet.collision.PhysicsCollisionEvent
  18. import com.jme3.bullet.collision.PhysicsCollisionListener
  19. import com.jme3.bullet.control.BetterCharacterControl
  20. import com.jme3.input.Action
  21. import com.jme3.input.InputManager
  22. import com.jme3.input.controls.ActionListener
  23. import com.jme3.input.controls.AnalogListener
  24. import com.jme3.input.controls.InputListener
  25. import com.jme3.input.controls.Trigger
  26. import com.jme3.light.Light
  27. import com.jme3.material.Material
  28. import com.jme3.math.Vector3f
  29. import com.jme3.scene.CameraNode
  30. import com.jme3.scene.Geometry
  31. import com.jme3.scene.Node
  32. import com.jme3.scene.SceneGraphVisitor
  33. import com.jme3.scene.Spatial
  34. import com.jme3.scene.control.CameraControl.ControlDirection
  35. import com.jme3.scene.control.Control
  36. import com.simsilica.es.EntityComponent
  37. import com.simsilica.es.EntityData
  38. import com.simsilica.es.EntityId
  39. import enumeratum._
  40. import monix.bio.Task
  41. import monix.bio.UIO
  42. import monix.execution.Ack
  43. import monix.execution.Ack.Continue
  44. import monix.execution.Ack.Stop
  45. import monix.execution.Cancelable
  46. import monix.execution.cancelables.SingleAssignCancelable
  47. import monix.reactive.Observable
  48. import monix.reactive.OverflowStrategy
  49. import monix.reactive.observers.Subscriber
  50. import org.slf4j.Logger
  51. import wow.doge.mygame.math.ImVector3f
  52. import wow.doge.mygame.state.MyBaseState
  53. final case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
  54. final case class EnumActionEvent[T <: EnumEntry](
  55. binding: T,
  56. value: Boolean,
  57. tpf: Float
  58. )
  59. final case class AnalogEvent(binding: Action, value: Float, tpf: Float)
  60. final case class EnumAnalogEvent[T <: EnumEntry](
  61. binding: T,
  62. value: Float,
  63. tpf: Float
  64. )
  65. final class PrePhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
  66. final class PhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
  67. package object implicits {
  68. type PrePhysicsTickEvent = PhysicsTickEvent
  69. type PhysicsTickObservable =
  70. Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
  71. implicit final class JMEAppExt(private val app: Application) extends AnyVal {
  72. def enqueueR(cb: () => Unit) =
  73. app.enqueue(new Runnable {
  74. override def run() = cb()
  75. })
  76. }
  77. implicit final class StateManagerExt(private val asm: AppStateManager)
  78. extends AnyVal {
  79. def state[S <: AppState]()(implicit c: ClassTag[S]): S =
  80. asm.getState(c.runtimeClass.asInstanceOf[Class[S]])
  81. // def appStates = asm.getStates()
  82. }
  83. implicit final class SimpleApplicationExt[T <: SimpleApplication](
  84. private val sa: T
  85. ) extends AnyVal {
  86. def stateManager: AppStateManager = sa.getStateManager()
  87. def inputManager: InputManager = sa.getInputManager()
  88. def assetManager: AssetManager = sa.getAssetManager()
  89. def guiNode = sa.getGuiNode()
  90. def flyCam = Option(sa.getFlyByCamera())
  91. def camera = sa.getCamera()
  92. def viewPort = sa.getViewPort()
  93. def rootNode = sa.getRootNode()
  94. def observableTick: Observable[Float] =
  95. Observable.create(OverflowStrategy.Unbounded) { sub =>
  96. val c = SingleAssignCancelable()
  97. val as = new MyBaseState {
  98. override def init(): Unit = {}
  99. override def update(tpf: Float) = {
  100. if (sub.onNext(tpf) == Ack.Stop)
  101. c.cancel()
  102. }
  103. override def stop(): Unit = {}
  104. override def onEnable() = {}
  105. override def onDisable() = {}
  106. }
  107. sa.stateManager.attach(as)
  108. c := Cancelable(() => sa.stateManager.detach(as))
  109. c
  110. }
  111. }
  112. implicit final class AssetManagerExt(private val am: AssetManager)
  113. extends AnyVal {
  114. def registerLocator(
  115. assetPath: os.RelPath,
  116. locator: Class[_ <: AssetLocator]
  117. ): Unit = {
  118. am.registerLocator(assetPath.toString(), locator)
  119. }
  120. def loadModel(assetPath: os.RelPath): Spatial = {
  121. am.loadModel(assetPath.toString())
  122. }
  123. }
  124. implicit final class BulletAppStateExt(private val bas: BulletAppState)
  125. extends AnyVal {
  126. def physicsSpace = bas.getPhysicsSpace()
  127. def speed = bas.getSpeed()
  128. }
  129. implicit final class BetterCharacterControlExt(
  130. private val bcc: BetterCharacterControl
  131. ) {
  132. def withJumpForce(force: ImVector3f) = {
  133. bcc.setJumpForce(force.mutable)
  134. bcc
  135. }
  136. }
  137. implicit final class SpatialExt[T <: Spatial](private val spat: T)
  138. extends AnyVal {
  139. // def asRef = Ref[Task].of(spat)
  140. }
  141. implicit final class NodeExt[T <: Node](private val n: T) extends AnyVal {
  142. /**
  143. * Attaches the given child
  144. *
  145. * @param s
  146. * @return
  147. */
  148. def withChild(s: Spatial): Node = {
  149. n.attachChild(s)
  150. n
  151. }
  152. /**
  153. * @return Gets the list of children as a monix observable
  154. */
  155. def observableChildren =
  156. Observable.fromIterable(n.getChildren().asScala)
  157. /**
  158. * @return A copy of the list of children of this node as a lazy list
  159. */
  160. def children = LazyList.from(n.getChildren().asScala)
  161. /**
  162. * Attach given children
  163. *
  164. * @param lst
  165. */
  166. def withChildren(lst: Spatial*): Node = {
  167. for (c <- lst) n.attachChild(c)
  168. n
  169. }
  170. def +=(spatial: Spatial) = n.attachChild(spatial)
  171. def :+(spatial: Spatial) = {
  172. n += spatial
  173. n
  174. }
  175. def :+(light: Light) = {
  176. n.addLight(light)
  177. n
  178. }
  179. def -=(spatial: Spatial) = n.detachChild(spatial)
  180. def :-(spatial: Spatial) = {
  181. n -= spatial
  182. n
  183. }
  184. def :-(light: Light) = {
  185. n.removeLight(light)
  186. n
  187. }
  188. def depthFirst(cb: Spatial => Unit) =
  189. n.depthFirstTraversal(new SceneGraphVisitor() {
  190. override def visit(s: Spatial) = cb(s)
  191. })
  192. def observableDepthFirst(): Observable[Spatial] = {
  193. def loop(
  194. subscriber: Subscriber[Spatial],
  195. spatials: LazyList[Spatial]
  196. ): Task[Unit] =
  197. spatials match {
  198. // spatial can be either a node or a geometry, but it's not a sealed trait
  199. case head #:: tail =>
  200. head match {
  201. case g: Geometry =>
  202. Task.deferFuture(subscriber.onNext(g)).flatMap {
  203. case Continue =>
  204. loop(subscriber, tail)
  205. case Stop =>
  206. Task(subscriber.onComplete())
  207. }
  208. case node: Node =>
  209. val children = node.children
  210. Task.deferFuture(subscriber.onNext(node)).flatMap {
  211. case Continue =>
  212. loop(subscriber, children #::: tail)
  213. case Stop =>
  214. Task(subscriber.onComplete())
  215. }
  216. // case _ => loop(subscriber, tail)
  217. }
  218. case LazyList() => Task.unit
  219. }
  220. Observable.create(OverflowStrategy.Unbounded) { sub =>
  221. implicit val sched = sub.scheduler
  222. loop(sub, LazyList(n)).runToFuture
  223. }
  224. }
  225. def breadthFirst(cb: Spatial => Unit) =
  226. n.breadthFirstTraversal(new SceneGraphVisitor() {
  227. override def visit(s: Spatial) = cb(s)
  228. })
  229. def observableBreadthFirst(): Observable[Spatial] = {
  230. def loop(
  231. subscriber: Subscriber[Spatial],
  232. spatials: LazyList[Spatial]
  233. ): Task[Unit] =
  234. spatials match {
  235. // spatial can be either a node or a geometry, but it's not a sealed trait
  236. case head #:: tail =>
  237. head match {
  238. case g: Geometry =>
  239. Task.deferFuture(subscriber.onNext(g)).flatMap {
  240. case Continue =>
  241. loop(subscriber, tail)
  242. case Stop =>
  243. Task(subscriber.onComplete())
  244. }
  245. case node: Node =>
  246. val children = node.children
  247. Task.deferFuture(subscriber.onNext(node)).flatMap {
  248. case Continue =>
  249. loop(subscriber, tail #::: children)
  250. case Stop =>
  251. Task(subscriber.onComplete())
  252. }
  253. case unknown =>
  254. Task.deferFuture(subscriber.onNext(unknown)).flatMap {
  255. case Continue =>
  256. loop(subscriber, tail)
  257. case Stop =>
  258. Task(subscriber.onComplete())
  259. }
  260. }
  261. case LazyList() => Task.unit
  262. }
  263. Observable.create(OverflowStrategy.Unbounded) { sub =>
  264. implicit val sched = sub.scheduler
  265. loop(sub, LazyList(n)).runToFuture
  266. }
  267. }
  268. def withControl[C <: Control](ctrl: C) = {
  269. n.addControl(ctrl)
  270. n
  271. }
  272. def withLocalTranslation(dir: ImVector3f) = {
  273. n.setLocalTranslation(dir.mutable)
  274. n
  275. }
  276. def withRotate(xAngle: Float, yAngle: Float, zAngle: Float) = {
  277. n.rotate(xAngle, yAngle, zAngle)
  278. n
  279. }
  280. def localRotation = n.getLocalRotation()
  281. def localTranslation = n.getLocalTranslation()
  282. }
  283. implicit final class CameraNodeExt(private val cn: CameraNode) {
  284. def withControlDir(controlDir: ControlDirection) = {
  285. cn.setControlDir(controlDir)
  286. cn
  287. }
  288. def withLookAt(position: ImVector3f, upVector: ImVector3f) = {
  289. cn.lookAt(position.mutable, upVector.mutable)
  290. cn
  291. }
  292. }
  293. implicit final class GeometryExt(private val geom: Geometry) {
  294. def withMaterial(mat: Material) = {
  295. geom.setMaterial(mat)
  296. geom
  297. }
  298. }
  299. implicit final class EntityDataExt(private val ed: EntityData)
  300. extends AnyVal {
  301. def query = new EntityQuery(ed)
  302. // def entities[T <: EntityComponent](entities: Seq[T])
  303. }
  304. implicit final class EntityExt(private val e: EntityId) extends AnyVal {
  305. def withComponents(classes: EntityComponent*)(implicit
  306. ed: EntityData
  307. ): EntityId = {
  308. ed.setComponents(e, classes: _*)
  309. e
  310. }
  311. }
  312. implicit final class ActorRefExt[Req](private val a: ActorRef[Req])
  313. extends AnyVal {
  314. import akka.actor.typed.scaladsl.AskPattern._
  315. /**
  316. * Same as [[ask]] but returns a [[Task]]
  317. */
  318. def askL[Res](
  319. replyTo: ActorRef[Res] => Req
  320. )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
  321. Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
  322. }
  323. def ??[Res](
  324. replyTo: ActorRef[Res] => Req
  325. )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
  326. askL(replyTo)
  327. /**
  328. * Same as [[tell]], but wrapped in a Task
  329. *
  330. * @param msg
  331. * @return
  332. */
  333. def tellL(msg: Req) = UIO(a.tell(msg))
  334. /**
  335. * Same as [[tell]], but wrapped in a Task
  336. *
  337. * @param msg
  338. * @return
  339. */
  340. def !!(msg: Req) = tellL(msg)
  341. }
  342. implicit final class InputManagerExt(private val inputManager: InputManager)
  343. extends AnyVal {
  344. /**
  345. * Create a new mapping to the given triggers.
  346. *
  347. * <p>
  348. * The given mapping will be assigned to the given triggers, when
  349. * any of the triggers given raise an event, the listeners
  350. * registered to the mappings will receive appropriate events.
  351. *
  352. * @param mappingName The mapping name to assign.
  353. * @param triggers The triggers to which the mapping is to be registered.
  354. */
  355. def withMapping(mapping: String, triggers: Trigger*): InputManager = {
  356. inputManager.addMapping(mapping, triggers: _*)
  357. inputManager
  358. }
  359. def withMapping[T <: EnumEntry](
  360. mapping: T,
  361. triggers: Trigger*
  362. ): InputManager = {
  363. inputManager.addMapping(mapping.entryName, triggers: _*)
  364. inputManager
  365. }
  366. def withListener(listener: InputListener, mappings: String*) = {
  367. inputManager.addListener(listener, mappings: _*)
  368. inputManager
  369. }
  370. /**
  371. * Creates new mappings from the values of the given Enum
  372. *
  373. * <p>
  374. * The given mapping will be assigned to the given triggers, when
  375. * any of the triggers given raise an event, the listeners
  376. * registered to the mappings will receive appropriate events.
  377. *
  378. * @param mappingName The mapping name to assign.
  379. * @param mappingFn Function from enum values to the sequence of trigers.
  380. *
  381. * @example
  382. *
  383. * {{{
  384. *
  385. * sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
  386. * object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
  387. * val values = findValues
  388. * case object TurnRight extends PlayerAnalogMovementInput
  389. * case object TurnLeft extends PlayerAnalogMovementInput
  390. * }
  391. *
  392. * {
  393. * inputManager.withEnumMappings(PlayerAnalogMovementInput) {
  394. * case PlayerAnalogMovementInput.TurnRight =>
  395. * Seq(new KeyTrigger(KeyInput.KEY_RIGHT))
  396. * case PlayerAnalogMovementInput.TurnLeft =>
  397. * Seq(new KeyTrigger(KeyInput.KEY_LEFT))
  398. * }
  399. * }
  400. * }}}
  401. */
  402. def withEnumMappings[T <: EnumEntry](
  403. mappingEnum: Enum[T]
  404. )(mappingFn: T => Seq[Trigger]): InputManager = {
  405. for (entry <- mappingEnum.values) {
  406. val mappings = mappingFn(entry)
  407. inputManager.addMapping(entry.entryName, mappings: _*)
  408. }
  409. inputManager
  410. }
  411. /**
  412. * Create an observable which emits the given mappings as elements of an observable
  413. *
  414. * @param mappingNames
  415. * @return Observable of action events
  416. *
  417. * @see [[ActionEvent]]
  418. * @see [[enumObservableAction]]
  419. */
  420. def observableAction(mappingNames: String*): Observable[ActionEvent] = {
  421. Observable.create(OverflowStrategy.DropOld(10)) { sub =>
  422. val c = SingleAssignCancelable()
  423. val al = new ActionListener {
  424. override def onAction(
  425. binding: String,
  426. value: Boolean,
  427. tpf: Float
  428. ): Unit = {
  429. if (
  430. sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
  431. ) {
  432. sub.onComplete()
  433. c.cancel()
  434. }
  435. }
  436. }
  437. inputManager.addListener(al, mappingNames: _*)
  438. c := Cancelable(() => inputManager.removeListener(al))
  439. c
  440. }
  441. }
  442. /**
  443. * <p>
  444. * Create an observable which emits the values of the given
  445. * enum as elements of an observable
  446. *
  447. * @param mappingNames
  448. * @return Observable of enum values
  449. *
  450. * @example {{{
  451. * inputManager
  452. * .enumObservableAction(PlayerMovementInput)
  453. * .doOnNext { action =>
  454. * action.binding match {
  455. * case PlayerMovementInput.WalkLeft => Task {/* your actions */}
  456. * }
  457. * }
  458. * }}}
  459. *
  460. * @see [[EnumActionEvent]]
  461. * @see [[enumAnalogObservable]]
  462. */
  463. def enumObservableAction[T <: EnumEntry](
  464. mappingEnum: Enum[T]
  465. ): Observable[EnumActionEvent[T]] = {
  466. Observable.create(OverflowStrategy.DropOld(10)) { sub =>
  467. val c = SingleAssignCancelable()
  468. val entryNames = mappingEnum.values.map(_.entryName)
  469. val al = new ActionListener {
  470. override def onAction(
  471. binding: String,
  472. value: Boolean,
  473. tpf: Float
  474. ): Unit = {
  475. mappingEnum.withNameOption(binding).foreach { b =>
  476. if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) {
  477. sub.onComplete()
  478. c.cancel()
  479. }
  480. }
  481. }
  482. }
  483. inputManager.addListener(al, entryNames: _*)
  484. c := Cancelable(() => inputManager.removeListener(al))
  485. c
  486. }
  487. }
  488. def enumEntryObservableAction[T <: EnumEntry](
  489. mappingEnumEntry: T
  490. ): Observable[EnumActionEvent[T]] = {
  491. Observable.create(OverflowStrategy.DropOld(10)) { sub =>
  492. val c = SingleAssignCancelable()
  493. val al = new ActionListener {
  494. override def onAction(
  495. binding: String,
  496. value: Boolean,
  497. tpf: Float
  498. ): Unit = {
  499. if (
  500. sub.onNext(
  501. EnumActionEvent(mappingEnumEntry, value, tpf)
  502. ) == Ack.Stop
  503. ) {
  504. sub.onComplete()
  505. c.cancel()
  506. }
  507. }
  508. }
  509. inputManager.addListener(al, mappingEnumEntry.entryName)
  510. c := Cancelable(() => inputManager.removeListener(al))
  511. c
  512. }
  513. }
  514. def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
  515. Observable.create(OverflowStrategy.DropOld(50)) { sub =>
  516. val c = SingleAssignCancelable()
  517. val al = new AnalogListener {
  518. override def onAnalog(
  519. binding: String,
  520. value: Float,
  521. tpf: Float
  522. ): Unit = {
  523. if (
  524. sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
  525. ) {
  526. sub.onComplete()
  527. c.cancel()
  528. }
  529. }
  530. }
  531. inputManager.addListener(al, mappingNames: _*)
  532. c := Cancelable(() => inputManager.removeListener(al))
  533. c
  534. }
  535. }
  536. def enumAnalogObservable[T <: EnumEntry](
  537. mappingEnum: Enum[T]
  538. ): Observable[EnumAnalogEvent[T]] = {
  539. Observable.create(OverflowStrategy.DropOld(50)) { sub =>
  540. val c = SingleAssignCancelable()
  541. val entryNames = mappingEnum.values.map(_.entryName)
  542. val al = new AnalogListener {
  543. override def onAnalog(
  544. binding: String,
  545. value: Float,
  546. tpf: Float
  547. ): Unit = {
  548. mappingEnum.withNameOption(binding).foreach { b =>
  549. if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) {
  550. sub.onComplete()
  551. c.cancel()
  552. }
  553. }
  554. }
  555. }
  556. inputManager.addListener(al, entryNames: _*)
  557. c := Cancelable(() => inputManager.removeListener(al))
  558. c
  559. }
  560. }
  561. }
  562. implicit final class PhysicsSpaceExt(private val space: PhysicsSpace)
  563. extends AnyVal {
  564. def collisionObservable(): Observable[PhysicsCollisionEvent] = {
  565. Observable.create(OverflowStrategy.DropOld(50)) { sub =>
  566. val c = SingleAssignCancelable()
  567. val cl = new PhysicsCollisionListener {
  568. override def collision(event: PhysicsCollisionEvent): Unit = {
  569. if (sub.onNext(event) == Ack.Stop) {
  570. sub.onComplete()
  571. c.cancel()
  572. }
  573. }
  574. }
  575. space.addCollisionListener(cl)
  576. c := Cancelable(() => space.removeCollisionListener(cl))
  577. c
  578. }
  579. }
  580. def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = {
  581. Observable.create(OverflowStrategy.DropOld(50)) { sub =>
  582. val c = SingleAssignCancelable()
  583. val cl = new PhysicsTickListener {
  584. override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
  585. val event = new PrePhysicsTickEvent(space)
  586. if (sub.onNext(event) == Ack.Stop) {
  587. sub.onComplete()
  588. c.cancel()
  589. }
  590. }
  591. override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {}
  592. }
  593. space.addTickListener(cl)
  594. c := Cancelable(() => space.removeTickListener(cl))
  595. c
  596. }
  597. }
  598. def physicsTickObservable(): Observable[PhysicsTickEvent] = {
  599. Observable.create(OverflowStrategy.DropOld(50)) { sub =>
  600. val c = SingleAssignCancelable()
  601. val cl = new PhysicsTickListener {
  602. override def prePhysicsTick(
  603. space: PhysicsSpace,
  604. tpf: Float
  605. ): Unit = {}
  606. override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
  607. val event = new PhysicsTickEvent(space)
  608. if (sub.onNext(event) == Ack.Stop) {
  609. sub.onComplete()
  610. c.cancel()
  611. }
  612. }
  613. }
  614. space.addTickListener(cl)
  615. c := Cancelable(() => space.removeTickListener(cl))
  616. c
  617. }
  618. }
  619. //TODO Consider creating a typeclass for this
  620. def +=(anyObject: Any) = space.add(anyObject)
  621. def :+(anyObject: Any) = {
  622. space.add(anyObject)
  623. space
  624. }
  625. def -(anyObject: Any) = {
  626. space.remove(anyObject)
  627. space
  628. }
  629. def +=(spatial: Spatial) = space.addAll(spatial)
  630. def :+(spatial: Spatial) = {
  631. space.addAll(spatial)
  632. space
  633. }
  634. def -(spatial: Spatial) = {
  635. space.removeAll(spatial)
  636. space
  637. }
  638. // def asRef = Ref[Task].of(space)
  639. }
  640. implicit final class Vector3fExt(private val v: Vector3f) extends AnyVal {
  641. //TODO add more operations
  642. def +=(that: Vector3f) = v.addLocal(that)
  643. def +=(f: Float) = v.addLocal(f, f, f)
  644. def +=(that: ImVector3f) = v.addLocal(that.x, that.y, that.z)
  645. def +=:(that: ImVector3f) = v += that
  646. def *=(that: Vector3f) = v.multLocal(that)
  647. def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z)
  648. def *=:(that: ImVector3f) = v *= that
  649. def -=(that: Vector3f) = v.subtractLocal(that)
  650. def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z)
  651. def -=:(that: ImVector3f) = v *= that
  652. def /=(that: Vector3f) = v.divideLocal(that)
  653. def /=(that: ImVector3f) = v.divideLocal(that.mutable)
  654. def /=:(that: ImVector3f) = v *= that
  655. def unary_- = v.negateLocal()
  656. def immutable = ImVector3f(v.x, v.y, v.z)
  657. }
  658. implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
  659. def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
  660. def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f)
  661. def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
  662. def *(f: Float): ImVector3f =
  663. v.copy(v.x * f, v.y * f, v.z * f)
  664. // v * ImVector3f(f, f, f)
  665. def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z)
  666. def -(f: Float): ImVector3f = v.copy(v.x - f, v.y - f, v.z - f)
  667. def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z)
  668. def /(f: Float): ImVector3f = v.copy(v.x / f, v.y / f, v.z / f)
  669. def unary_- = v.copy(-v.x, -v.y, -v.z)
  670. def mutable = new Vector3f(v.x, v.y, v.z)
  671. // /**
  672. // * alias for [[cross]] product
  673. // */
  674. // def |*|() = ()
  675. }
  676. // val TasktoUIO = new FunctionK[Task, UIO] {
  677. // def apply[T](f: Task[T]): UIO[T] =
  678. // f.hideErrors
  679. // }
  680. implicit final class JavaFxUIExt(private val jfxui: JavaFxUI) extends AnyVal {
  681. def +=(node: scalafx.scene.Node) = jfxui.attachChild(node)
  682. def -=(node: scalafx.scene.Node) = jfxui.detachChild(node)
  683. }
  684. implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal {
  685. def logP[T](
  686. x: sourcecode.Text[T],
  687. tag: String = "",
  688. width: Int = 100,
  689. height: Int = 500,
  690. indent: Int = 2,
  691. initialOffset: Int = 0
  692. )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
  693. // def joinSeq[T](seq: Seq[T], sep: T): Seq[T] = {
  694. // seq.flatMap(x => Seq(x, sep)).dropRight(1)
  695. // }
  696. val tagStrs =
  697. if (tag.isEmpty) Seq.empty
  698. else Seq(fansi.Color.Cyan(tag), fansi.Str(" "))
  699. val prefix = Seq(
  700. fansi.Color.Magenta(fileName.value),
  701. fansi.Str(":"),
  702. fansi.Color.Green(line.value.toString),
  703. fansi.Str(" "),
  704. fansi.Color.Cyan(x.source),
  705. fansi.Str(": ")
  706. ) ++ tagStrs
  707. fansi.Str.join(
  708. prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _*
  709. )
  710. // x.value
  711. }
  712. def warnP[T](
  713. s: sourcecode.Text[T]
  714. )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
  715. logger.warn(logP(s).render)
  716. s
  717. }
  718. def errorP[T](
  719. s: sourcecode.Text[T]
  720. )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
  721. logger.error(logP(s).render)
  722. s
  723. }
  724. def infoP[T](
  725. s: sourcecode.Text[T]
  726. )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
  727. logger.info(logP(s).render)
  728. s
  729. }
  730. def debugP[T](
  731. s: sourcecode.Text[T]
  732. )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
  733. logger.debug(logP(s).render)
  734. s
  735. }
  736. def traceP[T](
  737. s: sourcecode.Text[T]
  738. )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
  739. logger.trace(logP(s).render)
  740. s
  741. }
  742. }
  743. }