Skip to content

Commit 2e9f38b

Browse files
authored
Merge pull request #24 from scala-native/rewrite-timer
Rewrite Timer to be more performant
2 parents 56edf29 + b6bf541 commit 2e9f38b

File tree

9 files changed

+156
-151
lines changed

9 files changed

+156
-151
lines changed

build.sbt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ lazy val commonSettings = Seq(
4444
"-Ywarn-unused-import"
4545
),
4646
Compile / doc / scalacOptions -= "-Xfatal-warnings",
47+
libraryDependencies += "com.lihaoyi" %%% "utest" % "0.7.5-SNAPSHOT" % Test,
48+
testFrameworks += new TestFramework("utest.runner.Framework"),
49+
Test / nativeLinkStubs := true,
4750
publish / skip := true,
4851
publishLocal / skip := true
4952
)

core/loop.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ object EventLoop {
4747
@link("uv")
4848
@extern
4949
object LibUV {
50+
type UVHandle = Ptr[Byte]
5051
type PipeHandle = Ptr[Byte]
5152
type PollHandle = Ptr[Ptr[Byte]]
5253
type TCPHandle = Ptr[Byte]
@@ -66,7 +67,7 @@ object LibUV {
6667
type WriteCB = CFuncPtr2[WriteReq, Int, Unit]
6768
type PrepareCB = CFuncPtr1[PrepareHandle, Unit]
6869
type ShutdownCB = CFuncPtr2[ShutdownReq, Int, Unit]
69-
type CloseCB = CFuncPtr1[TCPHandle, Unit]
70+
type CloseCB = CFuncPtr1[UVHandle, Unit]
7071
type PollCB = CFuncPtr3[PollHandle, Int, Int, Unit]
7172
type TimerCB = CFuncPtr1[TimerHandle, Unit]
7273
type FSCB = CFuncPtr1[FSReq, Unit]
@@ -77,6 +78,8 @@ object LibUV {
7778
def uv_loop_close(loop: Loop): CInt = extern
7879
def uv_is_active(handle: Ptr[Byte]): Int = extern
7980
def uv_handle_size(h_type: Int): CSize = extern
81+
def uv_handle_get_data(handle: Ptr[Byte]): Long = extern
82+
def uv_handle_set_data(handle: Ptr[Byte], data: Long): Unit = extern
8083
def uv_req_size(r_type: Int): CSize = extern
8184
def uv_prepare_init(loop: Loop, handle: PrepareHandle): Int = extern
8285
def uv_prepare_start(handle: PrepareHandle, cb: PrepareCB): Int = extern
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package scala.scalanative.loop
2+
3+
import scala.scalanative.runtime.Intrinsics._
4+
import scala.scalanative.unsafe.Ptr
5+
import scala.scalanative.libc.stdlib
6+
import scala.collection.mutable
7+
import LibUV._
8+
9+
private [loop] object HandleUtils {
10+
private val references = mutable.Map.empty[Long, Int]
11+
12+
@inline def getData[T <: Object](handle: Ptr[Byte]): T = {
13+
val data = LibUV.uv_handle_get_data(handle)
14+
val rawptr = castLongToRawPtr(data)
15+
castRawPtrToObject(rawptr).asInstanceOf[T]
16+
}
17+
@inline def setData[T <: Object](handle: Ptr[Byte], function: T): Unit = {
18+
val rawptr = castObjectToRawPtr(function)
19+
val data = castRawPtrToLong(rawptr)
20+
if(references.contains(data)) references(data) += 1
21+
else references(data) = 1
22+
LibUV.uv_handle_set_data(handle, data)
23+
}
24+
private val onCloseCB = new CloseCB {
25+
def apply(handle: UVHandle): Unit = {
26+
stdlib.free(handle)
27+
}
28+
}
29+
@inline def close(handle: Ptr[Byte]): Unit = {
30+
uv_close(handle, onCloseCB)
31+
val data = LibUV.uv_handle_get_data(handle)
32+
val current = references(data)
33+
if(current > 1) references(data) -= 1
34+
else references.remove(data)
35+
}
36+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package scala.scalanative.loop
2+
3+
import scala.scalanative.libc.stdlib
4+
import scala.concurrent.{Future, Promise}
5+
import scala.concurrent.duration._
6+
import LibUV._, LibUVConstants._
7+
import scala.scalanative.unsafe.Ptr
8+
9+
@inline class Timer private (private val ptr: Ptr[Byte]) extends AnyVal {
10+
def clear(): Unit = {
11+
uv_timer_stop(ptr)
12+
HandleUtils.close(ptr)
13+
}
14+
}
15+
16+
object Timer {
17+
private val timeoutCB = new TimerCB {
18+
def apply(handle: TimerHandle): Unit = {
19+
val callback = HandleUtils.getData[() => Unit](handle)
20+
callback.apply()
21+
new Timer(handle)
22+
}
23+
}
24+
private val repeatCB = new TimerCB {
25+
def apply(handle: TimerHandle): Unit = {
26+
val callback = HandleUtils.getData[() => Unit](handle)
27+
callback.apply()
28+
}
29+
}
30+
@inline
31+
private def startTimer(timeout: Long, repeat: Long, callback: () => Unit): Timer = {
32+
val timerHandle = stdlib.malloc(uv_handle_size(UV_TIMER_T))
33+
uv_timer_init(EventLoop.loop, timerHandle)
34+
HandleUtils.setData(timerHandle, callback)
35+
uv_timer_start(timerHandle, timeoutCB, timeout, repeat)
36+
new Timer(timerHandle)
37+
}
38+
39+
def delay(duration: FiniteDuration): Future[Unit] = {
40+
val promise = Promise[Unit]()
41+
timeout(duration.toMillis)(() => promise.success(()))
42+
promise.future
43+
}
44+
45+
def timeout(millis: Long)(callback: () => Unit): Timer = {
46+
startTimer(millis, 0L, callback)
47+
}
48+
49+
def repeat(millis: Long)(callback: () => Unit): Timer = {
50+
startTimer(millis, millis, callback)
51+
}
52+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package scala.scalanative.loop
2+
3+
import utest._
4+
import scala.concurrent.duration._
5+
import scala.concurrent.ExecutionContext.Implicits.global
6+
import scala.concurrent.Promise
7+
8+
object TimerTests extends TestSuite {
9+
val tests = Tests {
10+
def now(): Duration = System.currentTimeMillis().millis
11+
val d = 200.millis
12+
test("delay") {
13+
var i = 0
14+
val startTime = now()
15+
def checkDelay(time: Int) = {
16+
i += 1
17+
assert(i == time)
18+
assert(now() - startTime >= d * time)
19+
}
20+
for {
21+
() <- Timer.delay(d)
22+
_ = checkDelay(1)
23+
() <- Timer.delay(d)
24+
_ = checkDelay(2)
25+
} yield ()
26+
}
27+
test("repeat") {
28+
var i = 0
29+
val startTime = now()
30+
val times = 3
31+
val p = Promise[Unit]()
32+
var timer: Timer = null.asInstanceOf[Timer]
33+
timer = Timer.repeat(d.toMillis) { () =>
34+
if(i == times) {
35+
p.success(())
36+
timer.clear()
37+
} else i += 1
38+
}
39+
p.future.map { _ =>
40+
assert(i == times)
41+
val took = now() - startTime
42+
assert(took >= d * 3)
43+
}
44+
}
45+
}
46+
}

core/timer.scala

Lines changed: 0 additions & 75 deletions
This file was deleted.

scalajs-compat/Handles.scala

Lines changed: 0 additions & 35 deletions
This file was deleted.

scalajs-compat/RawTimers.scala

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@
1212

1313
package scala.scalajs.js.timers
1414

15-
import scalanative.libc.stdlib
1615
import scalanative.loop.Timer
17-
import scalanative.loop.LibUV.uv_timer_stop
18-
import scalanative.loop.LibUVConstants.check
1916

2017
/**
2118
* <span class="badge badge-non-std" style="float: right;">Non-Standard</span>
@@ -34,22 +31,8 @@ object RawTimers {
3431
* @return A handle that can be used to cancel the timeout by passing it
3532
* to [[clearTimeout]].
3633
*/
37-
def setTimeout(handler: () => Unit, interval: Double): SetTimeoutHandle = {
38-
val t = Timer.oneTime(interval, handler)
39-
new SetTimeoutHandle {
40-
val timerHandle = t
41-
}
42-
}
43-
44-
/** Cancel a timeout execution
45-
* @param handle The handle returned by [[setTimeout]]
46-
*/
47-
def clearTimeout(handle: SetTimeoutHandle): Unit = {
48-
if (handle.timerHandle != null) {
49-
check(uv_timer_stop(handle.timerHandle), "uv_timer_stop")
50-
stdlib.free(handle.timerHandle)
51-
}
52-
}
34+
@inline def setTimeout(handler: () => Unit, interval: Double): SetTimeoutHandle =
35+
Timer.timeout(interval.toLong)(handler)
5336

5437
/** Schedule `handler` for repeated execution every `interval`
5538
* milliseconds.
@@ -59,20 +42,6 @@ object RawTimers {
5942
* @return A handle that can be used to cancel the interval by passing it
6043
* to [[clearInterval]].
6144
*/
62-
def setInterval(handler: () => Unit, interval: Double): SetIntervalHandle = {
63-
val t = Timer.repeat(interval, handler)
64-
new SetIntervalHandle {
65-
val timerHandle = t
66-
}
67-
}
68-
69-
/** Cancel an interval execution
70-
* @param handle The handle returned by [[setInterval]]
71-
*/
72-
def clearInterval(handle: SetIntervalHandle): Unit = {
73-
if (handle.timerHandle != null) {
74-
check(uv_timer_stop(handle.timerHandle), "uv_timer_stop")
75-
stdlib.free(handle.timerHandle)
76-
}
77-
}
45+
@inline def setInterval(handler: () => Unit, interval: Double): SetIntervalHandle =
46+
Timer.repeat(interval.toLong)(handler)
7847
}

scalajs-compat/package.scala

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import scala.concurrent.duration.FiniteDuration
2424
*/
2525
package object timers {
2626

27+
import scala.scalanative.loop.Timer
28+
29+
type SetTimeoutHandle = Timer
30+
type SetIntervalHandle = Timer
31+
2732
/** Schedule something for execution in `interval` milliseconds.
2833
*
2934
* @param interval duration in milliseconds to wait
@@ -32,8 +37,9 @@ package object timers {
3237
* to [[clearTimeout]].
3338
* @note Uses JavaScript's non-standard `setTimeout`
3439
*/
35-
def setTimeout(interval: Double)(body: => Unit): SetTimeoutHandle =
40+
def setTimeout(interval: Double)(body: => Unit): SetTimeoutHandle = {
3641
RawTimers.setTimeout(() => body, interval)
42+
}
3743

3844
/** Schedule something for execution after a duration.
3945
*
@@ -43,16 +49,16 @@ package object timers {
4349
* to [[clearTimeout]].
4450
* @note Uses JavaScript's non-standard `setTimeout`
4551
*/
46-
def setTimeout(interval: FiniteDuration)(body: => Unit): SetTimeoutHandle =
52+
def setTimeout(interval: FiniteDuration)(body: => Unit): SetTimeoutHandle = {
4753
RawTimers.setTimeout(() => body, interval.toMillis.toDouble)
54+
}
4855

4956
/** Cancel a timeout execution
5057
* @param handle The handle returned by
5158
* [[setTimeout(interval:scala\.concurrent\.duration\.FiniteDuration)* setTimeout]].
5259
* @note Uses JavaScript's non-standard `clearTimeout`
5360
*/
54-
def clearTimeout(handle: SetTimeoutHandle): Unit =
55-
RawTimers.clearTimeout(handle)
61+
def clearTimeout(handle: SetTimeoutHandle): Unit = handle.clear()
5662

5763
/** Schedule something for repeated execution every `interval` milliseconds.
5864
*
@@ -82,6 +88,6 @@ package object timers {
8288
* @note Uses JavaScript's non-standard `clearInterval`
8389
*/
8490
def clearInterval(handle: SetIntervalHandle): Unit =
85-
RawTimers.clearInterval(handle)
91+
handle.clear()
8692

8793
}

0 commit comments

Comments
 (0)