Skip to content
Snippets Groups Projects
Commit 3e0dd930 authored by Clément Pit-Claudel's avatar Clément Pit-Claudel
Browse files

client: Clean up implementation of Pause action and fix double wait


* js/src/main/scala/cs214/webapp/client/StateMachineClientApp.scala:
  (processing):
    New Boolean indicating that an event loop is running.
  (timeout, setCooldown, tryProcessNextAction):
    Remove.
  (processActions):
    New function: start an event loop, setting `processing` to `true` while
    traversing `actionsQueue`.  Recurse through a future to allow individual
    actions to delay the next iteration of the event loop.
  (processAction):
    Return a `Future` to let individual actions handle timeouts.
  (after):
    New function: Expose `setTimeout` as a Future.

Reported-by: default avatarSimon Lefort <androz2091@gmail.com>
parent 8970451f
No related branches found
No related tags found
1 merge request!22client: Clean up implementation of Pause action and fix double wait
...@@ -7,6 +7,9 @@ import scala.util.{Failure, Success} ...@@ -7,6 +7,9 @@ import scala.util.{Failure, Success}
import org.scalajs.dom import org.scalajs.dom
import scalatags.JsDom.all.Frag import scalatags.JsDom.all.Frag
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.concurrent.{Future, Promise}
type Target = dom.Element type Target = dom.Element
abstract class WSClientApp extends ClientApp: abstract class WSClientApp extends ClientApp:
...@@ -83,58 +86,41 @@ abstract class StateMachineClientAppInstance[Event, View]( ...@@ -83,58 +86,41 @@ abstract class StateMachineClientAppInstance[Event, View](
case Failure(msg) => WebClient.crash(Exception(msg)) case Failure(msg) => WebClient.crash(Exception(msg))
case Success(Failure(msg)) => renderError(msg.getMessage) case Success(Failure(msg)) => renderError(msg.getMessage)
case Success(Success(actions)) => actionsQueue.enqueueAll(actions) case Success(Success(actions)) => actionsQueue.enqueueAll(actions)
tryProcessNextAction() tryProcessActionsQueue()
/** The views to render */ /** Actions waiting to be processed. */
private val actionsQueue: mutable.Queue[Action[ujson.Value]] = mutable.Queue() private val actionsQueue: mutable.Queue[Action[ujson.Value]] = mutable.Queue()
private var processing = false
/** Minimal cooldown time between the render of two views */ private def tryProcessActionsQueue(): Unit =
private var actionCooldownDelay = 0 if !processing then processActions()
/** The timestamp for the last render of a view */
private var lastActionAppliedMs = System.currentTimeMillis()
/** A call to the [[tryProcessNextAction()]] function scheduled */
private var timeout: Option[SetTimeoutHandle] = None
/** Sets the [[actionCooldownDelay]] of the client to the specified value */ private def processActions(): Unit =
private def setCooldown(ms: Int): Unit = actionCooldownDelay = ms require(!processing)
def loop: Unit =
/** Apply the next action from [[actionsQueue]] if [[actionCooldownDelay]] processing = actionsQueue.nonEmpty
* permits it. if processing then
*/ processAction(actionsQueue.dequeue()).andThen(_ => loop)
private def tryProcessNextAction(): Unit = loop
// If there are still views to render and if no call to this function is scheduled
if actionsQueue.nonEmpty && timeout.isEmpty then
// Then check if we are out of the cooldown delay
if System.currentTimeMillis() - lastActionAppliedMs > actionCooldownDelay then
// We can render the view, and dequeue it
processAction(actionsQueue.dequeue())
// Continue try emptying the queue
tryProcessNextAction()
else
// We still have to wait, put a timeout to call the function later
timeout = Some(setTimeout(actionCooldownDelay) {
// First remove the timeout so that, if necessary,
// the next function call can create a new one
timeout = None
// Then try to render next views
tryProcessNextAction()
})
/** Execute a single action sent by the server. */ /** Execute a single action sent by the server. */
private def processAction(jsonAction: Action[ujson.Value]): Unit = private def processAction(jsonAction: Action[ujson.Value]): Future[Unit] =
lastActionAppliedMs = System.currentTimeMillis()
setCooldown(0)
jsonAction match jsonAction match
case Action.Alert(msg) => case Action.Alert(msg) =>
dom.window.alert(msg) dom.window.alert(msg)
after(0)
case Action.Pause(durationMs) => case Action.Pause(durationMs) =>
setCooldown(durationMs) after(durationMs)
case Action.Render(js) => case Action.Render(js) =>
// Step1: The client receives the view sent by the server here
wire.viewFormat.decode(js) match wire.viewFormat.decode(js) match
case Failure(exception) => renderError(exception.getMessage) case Failure(exception) => renderError(exception.getMessage)
case Success(jsonView) => case Success(jsonView) =>
target.replaceChildren: target.replaceChildren:
this.render(userId, jsonView).render this.render(userId, jsonView).render
after(0)
/* Like `setTimeout(interval)`, but return a `Future`. */
private def after(interval: Double): Future[Unit] =
val pr = Promise[Unit]()
setTimeout(interval)(pr.success(()))
pr.future
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment