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.

165 lines
4.7 KiB

4 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
3 years ago
4 years ago
4 years ago
4 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
  1. package wow.doge.mygame.subsystems.events
  2. import scala.reflect.ClassTag
  3. import scala.util.Random
  4. import akka.actor.typed.ActorRef
  5. import akka.actor.typed.Behavior
  6. import akka.actor.typed.Scheduler
  7. import akka.actor.typed.SpawnProtocol
  8. import akka.actor.typed.scaladsl.AskPattern._
  9. import akka.actor.typed.scaladsl.Behaviors
  10. import akka.event.EventStream
  11. import akka.util.Timeout
  12. import monix.bio.UIO
  13. import monix.execution.Ack
  14. import monix.execution.Cancelable
  15. import monix.execution.cancelables.SingleAssignCancelable
  16. import monix.reactive.Observable
  17. import monix.reactive.OverflowStrategy
  18. import wow.doge.mygame.implicits._
  19. import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
  20. import wow.doge.mygame.utils.AkkaUtils
  21. /**
  22. * A (typed) event bus
  23. * Copied (and repurposed) from Akka's EventStream
  24. */
  25. object EventBus {
  26. sealed trait Command[A]
  27. final case class Publish[A, E <: A](
  28. event: E,
  29. publisherName: String
  30. ) extends Command[A]
  31. final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit
  32. classTag: ClassTag[E]
  33. ) extends Command[A] {
  34. def topic: Class[_] = classTag.runtimeClass
  35. }
  36. final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E])
  37. extends Command[A]
  38. final case class ObservableSubscription[A, E <: A](
  39. replyTo: ActorRef[Observable[E]]
  40. )(implicit
  41. classTag: ClassTag[E]
  42. ) extends Command[A] {
  43. def ct = classTag
  44. }
  45. def apply[A: ClassTag]()(implicit
  46. timeout: Timeout,
  47. spawnProtocol: ActorRef[SpawnProtocol.Command]
  48. ): Behavior[EventBus.Command[A]] =
  49. Behaviors.setup { ctx =>
  50. val eventStream = new EventStream(ctx.system.classicSystem)
  51. implicit val scheduler = ctx.system.scheduler
  52. new EventBus().eventStreamBehavior(eventStream)
  53. }
  54. def observable[E](eventBus: ActorRef[EventBus.Command[E]])(implicit
  55. timeout: Timeout,
  56. scheduler: Scheduler,
  57. spawnProtocol: ActorRef[SpawnProtocol.Command],
  58. ct: ClassTag[E]
  59. ) =
  60. Observable.create[E](OverflowStrategy.DropOld(50)) { sub =>
  61. implicit val s = sub.scheduler
  62. val c = SingleAssignCancelable()
  63. val behavior = Behaviors.receive[E] { (ctx, msg) =>
  64. if (sub.onNext(msg) == Ack.Stop) {
  65. c.cancel()
  66. Behaviors.stopped
  67. } else Behaviors.same
  68. }
  69. val actor =
  70. AkkaUtils
  71. .spawnActorL(
  72. behavior,
  73. s"eventBusObservable-${ct.toString}-${Random.nextLong()}"
  74. )
  75. .mapError(err => new Exception(err.toString))
  76. .tapError {
  77. case ex => UIO(sub.onError(ex))
  78. }
  79. (for {
  80. a <- actor
  81. _ <- eventBus !! Subscribe(a)
  82. _ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a)))
  83. } yield ()).runToFuture
  84. c
  85. }
  86. def observable2[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit
  87. timeout: Timeout,
  88. scheduler: Scheduler,
  89. spawnProtocol: ActorRef[SpawnProtocol.Command],
  90. ct: ClassTag[A],
  91. ct2: ClassTag[B]
  92. ) =
  93. Observable.create[B](OverflowStrategy.DropOld(50)) { sub =>
  94. implicit val s = sub.scheduler
  95. val c = SingleAssignCancelable()
  96. val behavior = Behaviors.receive[B] { (ctx, msg) =>
  97. if (sub.onNext(msg) == Ack.Stop) {
  98. c.cancel()
  99. Behaviors.stopped
  100. } else Behaviors.same
  101. }
  102. val actor =
  103. AkkaUtils
  104. .spawnActorL(
  105. behavior,
  106. s"eventBusObservable-${ct.toString}-${math.abs(Random.nextLong())}"
  107. )
  108. .mapError(err => new Throwable(err.toString))
  109. .tapError {
  110. case ex => UIO(sub.onError(ex))
  111. }
  112. (for {
  113. a <- actor
  114. _ <- eventBus !! Subscribe(a)
  115. _ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a)))
  116. } yield ()).runToFuture
  117. c
  118. }
  119. }
  120. class EventBus[A] {
  121. import akka.actor.typed.scaladsl.adapter._
  122. private def eventStreamBehavior(
  123. eventStream: EventStream
  124. )(implicit
  125. timeout: Timeout,
  126. scheduler: Scheduler,
  127. spawnProtocol: ActorRef[SpawnProtocol.Command],
  128. ct: ClassTag[A]
  129. ): Behavior[EventBus.Command[A]] =
  130. Behaviors.setup { ctx =>
  131. Behaviors.receiveMessage {
  132. case EventBus.Publish(event, name) =>
  133. eventStream.publish(event)
  134. Behaviors.same
  135. case s @ EventBus.Subscribe(subscriber) =>
  136. eventStream.subscribe(subscriber.toClassic, s.topic)
  137. Behaviors.same
  138. case EventBus.Unsubscribe(subscriber) =>
  139. eventStream.unsubscribe(subscriber.toClassic)
  140. Behaviors.same
  141. case s @ ObservableSubscription(replyTo) =>
  142. val obs = EventBus.observable2(
  143. ctx.self
  144. )(timeout, scheduler, spawnProtocol, ct, s.ct)
  145. replyTo ! obs
  146. Behaviors.same
  147. }
  148. }
  149. }