Skip to content

Commit

Permalink
[core]: update KyoApp for JS
Browse files Browse the repository at this point in the history
Update for KyoApp in JS env, allow JS's KyoApp to use multiple runs
  • Loading branch information
HollandDM committed Nov 7, 2024
1 parent fe9268b commit 535a895
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 25 deletions.
30 changes: 30 additions & 0 deletions kyo-core/js/src/main/scala/kyo/KyoAppPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kyo

import kyo.Maybe.Absent
import kyo.Maybe.Present

abstract class KyoAppPlatformSpecific extends KyoApp.Base[Async & Resource & Abort[Throwable]]:

private var maybePreviousAsync: Maybe[Unit < (Async & Abort[Throwable])] = Absent

final override protected def run[A: Flat](v: => A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
import AllowUnsafe.embrace.danger
val currentAsync: Unit < (Async & Abort[Throwable]) =
Async.timeout(timeout)(handle(v)).map(value => printResult(Result.success(value)))
maybePreviousAsync = maybePreviousAsync match
case Absent => Present(currentAsync)
case Present(previousAsync) => Present(previousAsync.flatMap(_ => currentAsync))
if maybeProc.isEmpty then
maybeProc = Present(() =>
maybePreviousAsync match
case Absent => ()
case Present(previousAsync) =>
val racedAsyncIO = Clock.repeatWithDelay(1.hours)(()).map { fiber =>
Async.race(Seq(fiber.get, previousAsync))
}
val _ = IO.Unsafe.run(Async.run(racedAsyncIO)).eval
)
end if
end run

end KyoAppPlatformSpecific
22 changes: 22 additions & 0 deletions kyo-core/jvm/src/main/scala/kyo/KyoAppPlatformSpecific.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kyo

import scala.collection.mutable.ListBuffer

abstract class KyoAppPlatformSpecific extends KyoApp.Base[Async & Resource & Abort[Throwable]]:

private val procListBuffer = new ListBuffer[() => Unit]

final override protected def run[A: Flat](v: => A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
import AllowUnsafe.embrace.danger
procListBuffer += (() =>
val result = IO.Unsafe.run(Abort.run(Async.runAndBlock(timeout)(handle(v)))).eval
printResult(result)
)
if maybeProc.isEmpty then
maybeProc = Present(() =>
for proc <- procListBuffer do proc()
)
end if
end run

end KyoAppPlatformSpecific
30 changes: 16 additions & 14 deletions kyo-core/shared/src/main/scala/kyo/KyoApp.scala
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
package kyo

import scala.collection.mutable.ListBuffer

/** An abstract base class for Kyo applications.
*
* This class provides a foundation for building applications using the Kyo framework, with built-in support for logging, random number
* generation, and clock operations.
*
* Note: This class and its methods are unsafe and should only be used as the entrypoint of an application.
*/
abstract class KyoApp extends KyoApp.Base[Async & Resource & Abort[Throwable]]:
def timeout: Duration = Duration.Infinity
abstract class KyoApp extends KyoAppPlatformSpecific:
override def timeout: Duration = Duration.Infinity

override protected def handle[A: Flat](v: A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
import AllowUnsafe.embrace.danger
val result = KyoApp.Unsafe.runAndBlock(timeout)(v)
if !result.exists(().equals(_)) then
println(pprint.apply(result).plainText)
override protected def handle[A: Flat](v: A < (Async & Resource & Abort[Throwable]))(using Frame): A < (Async & Abort[Throwable]) =
Resource.run(v)
end handle
end KyoApp

Expand All @@ -30,21 +25,28 @@ object KyoApp:
* The effect type used by the application.
*/
abstract class Base[S]:
protected def timeout: Duration

protected def handle[A: Flat](v: A < S)(using Frame): Unit
protected def handle[A: Flat](v: A < S)(using Frame): A < (Async & Abort[Throwable])

final protected def args: Array[String] = _args

private var _args: Array[String] = null

private val initCode = new ListBuffer[() => Unit]
protected var maybeProc: Maybe[() => Unit] = Absent

final def main(args: Array[String]) =
this._args = args
for proc <- initCode do proc()
maybeProc match
case Present(proc) => proc()
case Absent => ()
end main

protected def run[A: Flat](v: => A < S)(using Frame): Unit

protected def run[A: Flat](v: => A < S)(using Frame): Unit =
initCode += (() => handle(v))
protected def printResult(result: Result[Any, Any]): Unit =
if !result.exists(().equals(_)) then println(pprint.apply(result).plainText)
end printResult
end Base

/** WARNING: Low-level API meant for integrations, libraries, and performance-sensitive code. See AllowUnsafe for more details. */
Expand Down
31 changes: 31 additions & 0 deletions kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import Tagged.*
import kyo.Clock.Deadline
import kyo.Clock.Stopwatch
import kyo.Clock.Unsafe
import kyo.internal.LayerMacros.Validated.succeed
import org.scalatest.compatible.Assertion
import scala.collection.mutable.ListBuffer
import scala.util.Try

class KyoAppTest extends Test:

Expand All @@ -30,6 +34,17 @@ class KyoAppTest extends Test:
runs <- ref.get
yield assert(runs == 3)
}
"multiple runs on JS" taggedAs jsOnly in {
val x = new ListBuffer[Int]
val promise = scala.concurrent.Promise[Assertion]()
val app = new KyoApp:
run { IO(x += 1) }
run { IO(x += 2) }
run { IO(x += 3) }
run { IO(promise.complete(Try(assert(x.toList == List(1, 2, 3))))) }
app.main(Array.empty)
promise.future
}
"effects" taggedAs jvmOnly in {
def run: Int < (Async & Resource & Abort[Throwable]) =
for
Expand All @@ -44,6 +59,22 @@ class KyoAppTest extends Test:
import AllowUnsafe.embrace.danger
assert(KyoApp.Unsafe.runAndBlock(Duration.Infinity)(run) == Result.success(1))
}
"effects on JS" taggedAs jsOnly in {
val promise = scala.concurrent.Promise[Assertion]()
val app = new KyoApp:
run {
for
_ <- Clock.repeatAtInterval(1.second, 1.second)(())
i <- Random.nextInt
_ <- Console.println(s"$i")
_ <- Clock.now
_ <- Resource.ensure(())
_ <- Async.sleep(1.second)
yield promise.complete(Try(succeed))
}
app.main(Array.empty)
promise.future
}
"failing effects" taggedAs jvmOnly in {
def run: Unit < (Async & Resource & Abort[Throwable]) =
for
Expand Down

This file was deleted.

10 changes: 6 additions & 4 deletions kyo-scheduler/js/src/main/scala/kyo/scheduler/util/Threads.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package kyo.scheduler.util

import java.lang.Thread
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadFactoryStub

object Threads {

def apply(name: String): ThreadFactory =
ThreadFactoryStub.get
def apply(name: String): ThreadFactory = apply(name, _ => Thread.currentThread())

def apply(name: String, create: Runnable => Thread): ThreadFactory =
ThreadFactoryStub.get
new ThreadFactory {
override def newThread(runnable: Runnable): Thread = create(runnable)
}

}

0 comments on commit 535a895

Please sign in to comment.