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.

908 lines
27 KiB

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