Refactored FX code into it's own class +
misc changes
This commit is contained in:
parent
029cbbd5ac
commit
6ef5c9778e
@ -1,14 +1,21 @@
|
|||||||
package nova.monadic_sfx
|
package nova.monadic_sfx
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
import com.softwaremill.macwire._
|
import com.softwaremill.macwire._
|
||||||
import io.odin.Logger
|
import io.odin.Logger
|
||||||
|
import monix.bio.IO
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
|
import monix.eval.Coeval
|
||||||
|
import monix.{eval => me}
|
||||||
import nova.monadic_sfx.executors.Schedulers
|
import nova.monadic_sfx.executors.Schedulers
|
||||||
import nova.monadic_sfx.implicits.JFXButton
|
import nova.monadic_sfx.implicits.JFXButton
|
||||||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._
|
import nova.monadic_sfx.implicits.JavaFXMonixObservables._
|
||||||
import nova.monadic_sfx.ui.MyFxApp
|
import nova.monadic_sfx.ui.MyFxApp
|
||||||
import nova.monadic_sfx.ui.components.router.BrainNotWorking
|
|
||||||
import nova.monadic_sfx.ui.components.router.FXRouter
|
import nova.monadic_sfx.ui.components.router.FXRouter
|
||||||
|
import nova.monadic_sfx.ui.components.router.Page
|
||||||
import nova.monadic_sfx.ui.components.todo.TodoListStore
|
import nova.monadic_sfx.ui.components.todo.TodoListStore
|
||||||
import nova.monadic_sfx.ui.components.todo.TodoListView
|
import nova.monadic_sfx.ui.components.todo.TodoListView
|
||||||
import org.gerweck.scalafx.util._
|
import org.gerweck.scalafx.util._
|
||||||
@ -18,12 +25,16 @@ import scalafx.beans.property.ObjectProperty
|
|||||||
import scalafx.beans.property.StringProperty
|
import scalafx.beans.property.StringProperty
|
||||||
import scalafx.collections.ObservableBuffer
|
import scalafx.collections.ObservableBuffer
|
||||||
import scalafx.geometry.Insets
|
import scalafx.geometry.Insets
|
||||||
|
import scalafx.geometry.Pos
|
||||||
|
import scalafx.scene.Parent
|
||||||
import scalafx.scene.Scene
|
import scalafx.scene.Scene
|
||||||
|
import scalafx.scene.control.Label
|
||||||
import scalafx.scene.control.TableColumn
|
import scalafx.scene.control.TableColumn
|
||||||
import scalafx.scene.control.TableView
|
import scalafx.scene.control.TableView
|
||||||
|
import scalafx.scene.layout.BorderPane
|
||||||
|
import scalafx.scene.layout.FlowPane
|
||||||
import scalafx.scene.layout.HBox
|
import scalafx.scene.layout.HBox
|
||||||
import scalafx.scene.paint.Color
|
import scalafx.scene.layout.Priority
|
||||||
import scalafx.scene.shape.Rectangle
|
|
||||||
|
|
||||||
class MainApp(
|
class MainApp(
|
||||||
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
||||||
@ -31,136 +42,108 @@ class MainApp(
|
|||||||
startTime: Long
|
startTime: Long
|
||||||
)(implicit logger: Logger[Task]) {
|
)(implicit logger: Logger[Task]) {
|
||||||
|
|
||||||
lazy val addTodoButton = new JFXButton {
|
|
||||||
text = "Add"
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val addTodoObs = addTodoButton.observableAction
|
|
||||||
|
|
||||||
// lazy val todoListView = TodoListView.defaultListView
|
|
||||||
|
|
||||||
lazy val _scene = new Scene {
|
lazy val _scene = new Scene {
|
||||||
root = new HBox {
|
root = new HBox {
|
||||||
padding = Insets(20)
|
padding = Insets(20)
|
||||||
content = new Rectangle {
|
|
||||||
width = 400
|
|
||||||
height = 200
|
|
||||||
fill = Color.DeepSkyBlue
|
|
||||||
}
|
|
||||||
children ++= Seq(
|
|
||||||
// new JFXButton {
|
|
||||||
// text = "DummyButton"
|
|
||||||
// },
|
|
||||||
// new JFXButton {
|
|
||||||
// text = "DummyButton2"
|
|
||||||
// },
|
|
||||||
// addTodoButton,
|
|
||||||
// Test.ttv
|
|
||||||
// todoListView
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy val stage = new PrimaryStage {
|
private lazy val stage = new PrimaryStage {
|
||||||
title = "Simple ScalaFX App"
|
title = "Simple ScalaFX App"
|
||||||
scene = _scene
|
scene = _scene
|
||||||
width = 1000
|
width = 640
|
||||||
height = 400
|
height = 480
|
||||||
}
|
}
|
||||||
|
|
||||||
// implicit val l = logger
|
|
||||||
// implicit val sp = spawnProtocol
|
|
||||||
|
|
||||||
val program = for {
|
val program = for {
|
||||||
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage)
|
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage)
|
||||||
// _ <- Task(fxApp.stage = stage)
|
_ <-
|
||||||
// .executeOn(schedulers.fx)
|
wire[MainAppDelegate].init
|
||||||
// .delayExecution(2000.millis)
|
.flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
|
||||||
// todoComponent <- createTodoComponent
|
|
||||||
// _ <- toIO(
|
|
||||||
// addTodoObs
|
|
||||||
// .mapEval(_ =>
|
|
||||||
// toTask(todoComponent.send(TodoListComponent.Add(Todo(1, "blah"))))
|
|
||||||
// )
|
|
||||||
// .completedL
|
|
||||||
// .executeOn(schedulers.fx)
|
|
||||||
// .startAndForget
|
|
||||||
// )
|
|
||||||
_ <- createTodoComponent.executeOn(schedulers.fx)
|
|
||||||
router <- Task(BrainNotWorking.router)
|
|
||||||
routerStore <- router.store(BrainNotWorking.Page.Home, logger)
|
|
||||||
routerNode <- for {
|
|
||||||
node <-
|
|
||||||
Task
|
|
||||||
.deferAction(implicit s =>
|
|
||||||
Task(new HBox {
|
|
||||||
children <-- router
|
|
||||||
.render(BrainNotWorking.resolver)(routerStore)
|
|
||||||
.map(_.delegate)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.executeOn(schedulers.fx)
|
.executeOn(schedulers.fx)
|
||||||
_ <- Task.deferFuture(
|
currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS)
|
||||||
routerStore.onNext(FXRouter.Replace(BrainNotWorking.Page.UserHome(1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
} yield node
|
|
||||||
// _ <-
|
|
||||||
// BrainNotWorking
|
|
||||||
// .routerTask(logger)
|
|
||||||
// .flatMap(node => Task(_scene.getChildren += node))
|
|
||||||
// .executeOn(schedulers.fx)
|
|
||||||
_ <- Task(_scene.getChildren += routerNode).executeOn(schedulers.fx)
|
|
||||||
_ <- logger.info(
|
_ <- logger.info(
|
||||||
s"Application started in ${(System.currentTimeMillis() - startTime) / 1000f} seconds"
|
s"Application started in ${(currentTime - startTime) / 1000f} seconds"
|
||||||
)
|
)
|
||||||
_ <- fxAppFib.join
|
_ <- fxAppFib.join
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
// def createTodoComponent: Task[TodoListComponent] = {
|
}
|
||||||
// for {
|
|
||||||
// channel <-
|
|
||||||
// ConcurrentChannel
|
|
||||||
// .of[Task, TodoListComponent.Complete, TodoListComponent.Command]
|
|
||||||
// scheduler = schedulers.fx
|
|
||||||
// lv <- TodoListView.defaultListView2.executeOn(scheduler)
|
|
||||||
// // todoLV = new TodoListView(lv)
|
|
||||||
// todoComponent <- wire[TodoListComponent.Props].create
|
|
||||||
// // TODO make this a "message pass" instead of mutating directly
|
|
||||||
// _ <- Task(_scene.getChildren += lv).executeOn(scheduler)
|
|
||||||
// // _ <- toIO(
|
|
||||||
// // delObs
|
|
||||||
// // .doOnNext(_ => toTask(logger.debug("Pressed delete")))
|
|
||||||
// // .doOnNext(todo =>
|
|
||||||
// // toTask(
|
|
||||||
// // for {
|
|
||||||
// // _ <- logger.debug(s"Got todo $todo")
|
|
||||||
// // _ <- todoComponent.send(TodoListComponent.Delete(todo.id))
|
|
||||||
// // // _ <- Task.sequence(
|
|
||||||
// // // lst.map(todo =>
|
|
||||||
// // // todoComponent.send(TodoListComponent.Delete(todo.id))
|
|
||||||
// // // )
|
|
||||||
// // // )
|
|
||||||
// // } yield ()
|
|
||||||
// // )
|
|
||||||
// // )
|
|
||||||
// // .completedL
|
|
||||||
// // ).startAndForget
|
|
||||||
// // _ <- toIO(
|
|
||||||
// // editObs
|
|
||||||
// // .doOnNext(_ => toTask(logger.debug("Pressed edit")))
|
|
||||||
// // .completedL
|
|
||||||
// // ).startAndForget
|
|
||||||
// } yield todoComponent
|
|
||||||
// }
|
|
||||||
|
|
||||||
def createTodoComponent: Task[Unit] =
|
class MainAppDelegate(schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
||||||
|
val buttonStyle = """| -fx-padding: 0.7em 0.57em;
|
||||||
|
| -fx-font-size: 14px;
|
||||||
|
| -jfx-button-type: RAISED;
|
||||||
|
| -fx-background-color: rgb(77,102,204);
|
||||||
|
| -fx-pref-width: 200;
|
||||||
|
| -fx-text-fill: WHITE; """.stripMargin
|
||||||
|
|
||||||
|
def init =
|
||||||
for {
|
for {
|
||||||
store <- TodoListStore(logger)
|
router <- Task.pure(new FXRouter[Page])
|
||||||
rootNode <- TodoListView(store)
|
routerStore <- router.store(Page.Home, logger)
|
||||||
_ <- Task(_scene.getChildren += rootNode)
|
todoStore <- TodoListStore(logger)
|
||||||
} yield ()
|
todoComponent <- TodoListView(todoStore)
|
||||||
|
resolver: PartialFunction[Page, Task[Parent]] = {
|
||||||
|
case Page.Home =>
|
||||||
|
Task(new Label {
|
||||||
|
text = "HomePage"
|
||||||
|
})
|
||||||
|
case Page.UserHome(id0) =>
|
||||||
|
Task(new Label {
|
||||||
|
text = s"User Home, Id = $id0"
|
||||||
|
})
|
||||||
|
case Page.Todo =>
|
||||||
|
Task(todoComponent)
|
||||||
|
}
|
||||||
|
routerNode <-
|
||||||
|
Task
|
||||||
|
.deferAction(implicit s =>
|
||||||
|
Task(new HBox {
|
||||||
|
//TODO find a better way to do this
|
||||||
|
var oldValue: Option[Parent] = None
|
||||||
|
children <-- router
|
||||||
|
.render(resolver)(routerStore)
|
||||||
|
// call cancel on the old component to cancel all subscriptions
|
||||||
|
.doOnNextF(newValue =>
|
||||||
|
Coeval { oldValue.foreach(_ => ()) } >> Coeval {
|
||||||
|
oldValue = Some(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map(_.delegate)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
mainSceneNode <- Task.deferAction(implicit s =>
|
||||||
|
Task(new BorderPane {
|
||||||
|
hgrow = Priority.Always
|
||||||
|
vgrow = Priority.Always
|
||||||
|
center = routerNode
|
||||||
|
bottom = new FlowPane {
|
||||||
|
alignment = Pos.Center
|
||||||
|
hgap = 20
|
||||||
|
children = Seq(
|
||||||
|
new JFXButton {
|
||||||
|
text = "Todo"
|
||||||
|
style = buttonStyle
|
||||||
|
obsAction
|
||||||
|
.useLazyEval(
|
||||||
|
me.Task.pure(FXRouter.Replace(Page.Todo))
|
||||||
|
) --> routerStore
|
||||||
|
},
|
||||||
|
new JFXButton {
|
||||||
|
text = "UserHome"
|
||||||
|
style = buttonStyle
|
||||||
|
obsAction
|
||||||
|
.useLazyEval(
|
||||||
|
me.Task(FXRouter.Replace(Page.UserHome(Random.nextInt(20))))
|
||||||
|
) --> routerStore
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} yield mainSceneNode
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestModel(_name: String, _age: Int) {
|
class TestModel(_name: String, _age: Int) {
|
||||||
|
@ -19,23 +19,23 @@ trait MainModule extends ActorModule with UiModule with HttpModule {
|
|||||||
"nova.monadic_sfx.util.reactive.Store" -> storeLogger
|
"nova.monadic_sfx.util.reactive.Store" -> storeLogger
|
||||||
)
|
)
|
||||||
.withFallback(defaultLogger)
|
.withFallback(defaultLogger)
|
||||||
.withAsync()
|
.withAsync(timeWindow = 1.millis)
|
||||||
|
|
||||||
def makeLogger =
|
def makeLogger =
|
||||||
for {
|
for {
|
||||||
defaultLogger <- consoleLogger[Task]()
|
defaultLogger <- consoleLogger[Task]()
|
||||||
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
|
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
|
||||||
"application.log"
|
"application.log"
|
||||||
).withAsync()
|
).withAsync(timeWindow = 1.millis)
|
||||||
middlewareLogger <-
|
middlewareLogger <-
|
||||||
consoleLogger[
|
consoleLogger[
|
||||||
Task
|
Task
|
||||||
](formatter = Middlewares.format)
|
](formatter = Middlewares.format)
|
||||||
.withMinimalLevel(Level.Trace)
|
.withMinimalLevel(Level.Trace)
|
||||||
.withAsync() |+| fileLogger[Task](
|
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
|
||||||
"stores.log",
|
"stores.log",
|
||||||
formatter = Middlewares.format
|
formatter = Middlewares.format
|
||||||
).withAsync()
|
).withAsync(timeWindow = 1.millis)
|
||||||
routerLogger <- routerLogger(defaultLogger, middlewareLogger)
|
routerLogger <- routerLogger(defaultLogger, middlewareLogger)
|
||||||
} yield (routerLogger)
|
} yield (routerLogger)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class ActionObservableExecutor[T](
|
|||||||
) extends AnyVal {
|
) extends AnyVal {
|
||||||
def -->(sub: Observer[T])(implicit s: Scheduler) =
|
def -->(sub: Observer[T])(implicit s: Scheduler) =
|
||||||
delegate
|
delegate
|
||||||
.doOnNext(el => me.Task(sub.onNext(el)))
|
.doOnNext(el => me.Task.deferFuture(sub.onNext(el)) >> me.Task.unit)
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,8 @@ class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
|||||||
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) =
|
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) =
|
||||||
for {
|
for {
|
||||||
_ <- logger.info("Starting FX App")
|
_ <- logger.info("Starting FX App")
|
||||||
fib <- Task(internal.main(Array.empty)).start
|
fib <-
|
||||||
|
Task(internal.main(Array.empty)).start.executeOn(schedulers.blocking)
|
||||||
_ <- Task.sleep(200.millis)
|
_ <- Task.sleep(200.millis)
|
||||||
_ <- Task(internal.stage = stage)
|
_ <- Task(internal.stage = stage)
|
||||||
.executeOn(schedulers.fx)
|
.executeOn(schedulers.fx)
|
||||||
|
@ -1,47 +1,52 @@
|
|||||||
package nova.monadic_sfx.ui.components.router
|
package nova.monadic_sfx.ui.components.router
|
||||||
|
|
||||||
import enumeratum._
|
import io.circe.Codec
|
||||||
|
import io.circe.Decoder
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
import io.circe.generic.JsonCodec
|
import io.circe.generic.JsonCodec
|
||||||
|
import io.circe.generic.semiauto._
|
||||||
import io.odin.Logger
|
import io.odin.Logger
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
import nova.monadic_sfx.util.IOUtils
|
import nova.monadic_sfx.util.IOUtils
|
||||||
|
import nova.monadic_sfx.util.reactive.Middlewares
|
||||||
import nova.monadic_sfx.util.reactive.Reducer
|
import nova.monadic_sfx.util.reactive.Reducer
|
||||||
import nova.monadic_sfx.util.reactive.Store
|
import nova.monadic_sfx.util.reactive.Store
|
||||||
import scalafx.scene.Parent
|
import scalafx.scene.Parent
|
||||||
import scalafx.scene.control.Label
|
|
||||||
|
|
||||||
object FXRouter {
|
object FXRouter {
|
||||||
|
|
||||||
final case class State[P](page: P)
|
final case class State[P](page: P)
|
||||||
|
|
||||||
@JsonCodec
|
// @JsonCodec
|
||||||
sealed abstract class Action[T]
|
sealed abstract class Action[+T]
|
||||||
// final case object Init extends Action
|
|
||||||
final case class Replace[T](p: T) extends Action[T]
|
final case class Replace[T](p: T) extends Action[T]
|
||||||
|
|
||||||
|
object Action {
|
||||||
|
implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec
|
||||||
|
}
|
||||||
|
|
||||||
type FXStore[P] = Store[Action[P], State[P]]
|
type FXStore[P] = Store[Action[P], State[P]]
|
||||||
|
|
||||||
// def resolver2 = resolver.lift.andThen(_.getOrElse(notFound))
|
|
||||||
|
|
||||||
// def resolver: PartialFunction[P <: Enum[P]][P, Parent] = {
|
|
||||||
// case Home => new TextField
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FXRouter[P <: EnumEntry](
|
class FXRouter[P](
|
||||||
)(implicit E: Encoder[P]) {
|
)(implicit E: Encoder[P], D: Decoder[P]) {
|
||||||
import FXRouter._
|
import FXRouter._
|
||||||
|
|
||||||
def store(initialPage: P, logger: Logger[Task]) =
|
def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] =
|
||||||
Task.deferAction(implicit s =>
|
Task.deferAction(implicit s =>
|
||||||
Store.createL[Action[P], State[P]](
|
for {
|
||||||
|
mw <- Middlewares.actionLoggerMiddleware[Action[P], State[P]](
|
||||||
|
logger,
|
||||||
|
"RouterStore"
|
||||||
|
)
|
||||||
|
store <- Store.createL[Action[P], State[P]](
|
||||||
Replace(initialPage),
|
Replace(initialPage),
|
||||||
State(initialPage),
|
State(initialPage),
|
||||||
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _)
|
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _),
|
||||||
// Seq(actionLoggerMiddleware(logger, "RouterStore"))
|
Seq(mw)
|
||||||
)
|
)
|
||||||
|
} yield store
|
||||||
)
|
)
|
||||||
|
|
||||||
def reducer(
|
def reducer(
|
||||||
@ -67,28 +72,12 @@ class FXRouter[P <: EnumEntry](
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object BrainNotWorking {
|
@JsonCodec
|
||||||
|
sealed trait Page
|
||||||
@JsonCodec
|
object Page {
|
||||||
sealed trait Page extends EnumEntry
|
|
||||||
object Page extends Enum[Page] {
|
|
||||||
val values = findValues
|
|
||||||
final case object Home extends Page
|
final case object Home extends Page
|
||||||
final case class UserHome(id: Int) extends Page
|
final case class UserHome(id: Int) extends Page
|
||||||
}
|
final case object Todo extends Page
|
||||||
|
|
||||||
def resolver: PartialFunction[Page, Task[Parent]] = {
|
|
||||||
case Page.Home =>
|
|
||||||
Task(new Label {
|
|
||||||
text = "HomePage"
|
|
||||||
})
|
|
||||||
case Page.UserHome(id0) =>
|
|
||||||
Task(new Label {
|
|
||||||
text = s"User Home, Id = $id0"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val router = new FXRouter[Page]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// case class State()
|
// case class State()
|
||||||
|
@ -16,7 +16,7 @@ import scalafx.Includes._
|
|||||||
import scalafx.beans.property.ObjectProperty
|
import scalafx.beans.property.ObjectProperty
|
||||||
import scalafx.beans.property.StringProperty
|
import scalafx.beans.property.StringProperty
|
||||||
import scalafx.geometry.Insets
|
import scalafx.geometry.Insets
|
||||||
import scalafx.scene.Node
|
import scalafx.scene.Parent
|
||||||
import scalafx.scene.control.ContextMenu
|
import scalafx.scene.control.ContextMenu
|
||||||
import scalafx.scene.control.ListCell
|
import scalafx.scene.control.ListCell
|
||||||
import scalafx.scene.control.SelectionMode
|
import scalafx.scene.control.SelectionMode
|
||||||
@ -25,17 +25,12 @@ import scalafx.scene.text.Text
|
|||||||
|
|
||||||
object TodoListView {
|
object TodoListView {
|
||||||
def apply(
|
def apply(
|
||||||
store: MonixProSubject[
|
store: Store[TodoListStore.Action, TodoListStore.State]
|
||||||
TodoListStore.Action,
|
): Task[Parent] =
|
||||||
(TodoListStore.Action, TodoListStore.State)
|
|
||||||
]
|
|
||||||
): Task[Node] =
|
|
||||||
Task.deferAction(implicit s =>
|
Task.deferAction(implicit s =>
|
||||||
Task {
|
Task {
|
||||||
val cc = CompositeCancelable()
|
val cc = CompositeCancelable()
|
||||||
val todos =
|
val todos = store.map { case (_, state) => state.todos }
|
||||||
store.map { case (_, state) => state.todos }
|
|
||||||
// Todo(-1, "").some
|
|
||||||
val _selectedItems = ObjectProperty(Seq.empty[Todo])
|
val _selectedItems = ObjectProperty(Seq.empty[Todo])
|
||||||
|
|
||||||
new HBox {
|
new HBox {
|
||||||
|
44
src/main/scala/nova/monadic_sfx/util/SynchedObject.scala
Normal file
44
src/main/scala/nova/monadic_sfx/util/SynchedObject.scala
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package nova.monadic_sfx.util
|
||||||
|
|
||||||
|
import monix.bio.Task
|
||||||
|
import monix.bio.UIO
|
||||||
|
import monix.catnap.MVar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronization wrapper for a mutable object
|
||||||
|
*
|
||||||
|
* @param obj the mutable object
|
||||||
|
* @param lock lock for synchronization
|
||||||
|
*/
|
||||||
|
class SynchedObject[A](obj: A, lock: MLock) {
|
||||||
|
|
||||||
|
def modify(f: A => Task[Unit]): Task[Unit] =
|
||||||
|
lock.greenLight(f(obj))
|
||||||
|
|
||||||
|
def get: Task[A] = lock.greenLight(Task(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
object SynchedObject {
|
||||||
|
def apply[A](obj: A) =
|
||||||
|
MVar[Task]
|
||||||
|
.of(())
|
||||||
|
.map(m => new MLock(m))
|
||||||
|
.flatMap(lock => Task(new SynchedObject(obj, lock)))
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MLock(mvar: MVar[Task, Unit]) {
|
||||||
|
def acquire: Task[Unit] =
|
||||||
|
mvar.take
|
||||||
|
|
||||||
|
def release: Task[Unit] =
|
||||||
|
mvar.put(())
|
||||||
|
|
||||||
|
def greenLight[A](fa: Task[A]): Task[A] =
|
||||||
|
for {
|
||||||
|
_ <- acquire
|
||||||
|
a <- fa.doOnCancel(
|
||||||
|
release.onErrorHandleWith(ex => UIO(println(ex.getMessage())))
|
||||||
|
)
|
||||||
|
_ <- release
|
||||||
|
} yield a
|
||||||
|
}
|
@ -21,7 +21,7 @@ import monix.reactive.Observable
|
|||||||
final case class StoreInfo[A](
|
final case class StoreInfo[A](
|
||||||
name: String,
|
name: String,
|
||||||
action: A,
|
action: A,
|
||||||
time: LocalDateTime = LocalDateTime.now()
|
time: LocalDateTime
|
||||||
)
|
)
|
||||||
|
|
||||||
object StoreInfo {
|
object StoreInfo {
|
||||||
@ -68,7 +68,8 @@ object Middlewares {
|
|||||||
Task((obs: Observable[(A, M)]) =>
|
Task((obs: Observable[(A, M)]) =>
|
||||||
obs.doOnNextF {
|
obs.doOnNextF {
|
||||||
case (a, _) =>
|
case (a, _) =>
|
||||||
logger.debug(StoreInfo(name, a))
|
Task(LocalDateTime.now())
|
||||||
|
.flatMap(curTime => logger.debug(StoreInfo(name, a, curTime)))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package nova.monadic_sfx.util.reactive
|
package nova.monadic_sfx.util.reactive
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
import io.circe.Encoder
|
import io.circe.Encoder
|
||||||
import io.odin.Logger
|
import io.odin.Logger
|
||||||
import monix.bio.Task
|
import monix.bio.Task
|
||||||
@ -72,9 +74,11 @@ object Store {
|
|||||||
|
|
||||||
val obs = subject
|
val obs = subject
|
||||||
.doOnNextF(action =>
|
.doOnNextF(action =>
|
||||||
|
Task(LocalDateTime.now()).flatMap(curTime =>
|
||||||
logger.debug(
|
logger.debug(
|
||||||
StoreInfo(storeName, action)
|
StoreInfo(storeName, action, curTime)
|
||||||
) // .executeOn(Scheduler.global)
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
// .doOnNextF(action => Coeval(println(action)))
|
// .doOnNextF(action => Coeval(println(action)))
|
||||||
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))(
|
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))(
|
||||||
|
Loading…
Reference in New Issue
Block a user