Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ScalaJS's KyoApp #807

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 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,26 @@
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]) =
Abort.run(handle(v)).map(result => IO(printResult(result)).andThen(Abort.get(result)).unit)
maybePreviousAsync = maybePreviousAsync match
case Absent => Present(currentAsync)
case Present(previousAsync) => Present(previousAsync.map(_ => currentAsync))
hearnadam marked this conversation as resolved.
Show resolved Hide resolved
initCode = maybePreviousAsync.map { previousAsync => () =>
val racedAsyncIO = Clock.repeatWithDelay(1.hour)(()).map { fiber =>
val race = Async.race(Seq(fiber.get, previousAsync))
Async.timeout(timeout)(race)
}
val _ = IO.Unsafe.run(Async.run(racedAsyncIO)).eval
}.toList
end run

end KyoAppPlatformSpecific
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kyo

import scala.collection.mutable.ListBuffer

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

final override protected def run[A: Flat](v: => A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
import AllowUnsafe.embrace.danger
initCode = initCode.appended(() =>
val result = IO.Unsafe.run(Abort.run(Async.runAndBlock(timeout)(handle(v)))).eval
printResult(result)
)
end run

end KyoAppPlatformSpecific
26 changes: 13 additions & 13 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,26 @@ 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 initCode: List[() => Unit] = List.empty

final def main(args: Array[String]) =
this._args = args
for proc <- initCode do proc()
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
38 changes: 36 additions & 2 deletions kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package kyo

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 @@ -27,20 +34,47 @@ class KyoAppTest extends Test:
runs <- ref.get
yield assert(runs == 3)
}
"effects" taggedAs jvmOnly in {
"ordered runs" in {
val x = new ListBuffer[Int]
val promise = scala.concurrent.Promise[Assertion]()
val app = new KyoApp:
run { Async.delay(10.millis)(IO(x += 1)) }
run { Async.delay(10.millis)(IO(x += 2)) }
run { Async.delay(10.millis)(IO(x += 3)) }
run { IO(promise.complete(Try(assert(x.toList == List(1, 2, 3))))) }
app.main(Array.empty)
promise.future
}
"effects in JVM" taggedAs jvmOnly in {
def run: Int < (Async & Resource & Abort[Throwable]) =
for
_ <- Clock.repeatAtInterval(1.second, 1.second)(())
i <- Random.nextInt
_ <- Console.printLine(s"$i")
_ <- Clock.now
_ <- Resource.ensure(())
_ <- Async.run(())
_ <- Async.sleep(1.second)
yield 1

import AllowUnsafe.embrace.danger
assert(KyoApp.Unsafe.runAndBlock(Duration.Infinity)(run) == Result.success(1))
}
"effects in 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.printLine(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)
}

}
Loading