#1784 Timer loops

SlimerDude Fri 24 Feb 2012

How may I set up a timer loop?

I'm thinking in terms of, say, doing a long running animation in Fwt. The Docs mention using Actor#sendLater but I can't see this works in a loop situation.

Essentially I want something like:

const class Loopy : Actor {
  Bool running := true

  override Obj? receive(Obj? o) {
    while (running) {
      Actor.sleep(Duration(100000000))
      ... do stuff ...
    }  
    return null
  }
}

But being an Actor, when in the loop, it can't receive any control messages (to change running)

I've thought about calling out to a Service, but each Actor has it's own ThreadLocal version of the Service state.

Feeling lost...

qualidafial Fri 24 Feb 2012

SlimerDude Fri 24 Feb 2012

Okay, the answer is a little verbose but seems to work...

You have another Actor class that does nothing but hold your shared state:

const class LoopState : Actor {
  new make(ActorPool ap) : super(ap) {}

  override Obj? receive(Obj? o) {
    Obj[] l := o
    if (l[0] == "get")
      return Actor.locals["wotever"]
    if (l[0] == "set")
      Actor.locals["wotever"] = l[1]
    return null
  }
}

The initiating method creates the Loop Actor with the above stateful actor:

ls := LoopState(ActorPool())
l := Loopy(ActorPool(), ls)

Both threads can then set / get data on the stateful actor. e.g.

ls.send(["set", "Sausage Cakes"])
Str wotever := ls.send(["get"]).get

Is this the right way to go about it, or is there a better way?

Cheers.

KevinKelley Fri 24 Feb 2012

In your fan-home, examples/fwt/clock.fan is like that, see if it helps.

Two things: for state variables, use Actor.locals (a map local to the actor where you can stash stuff under a name):

Actor.locals["running"] = true
running := Actor.locals["running"] as Bool
  ...

Other thing, if you loop inside your receive, you'll block the message queue -- you're supposed to handle the message and get out, so the next message can come thru. So do a sendLater to post the next update.

brian Fri 24 Feb 2012

As @qualidafial said, in FWT you can use callLater. In terms of actors...

But being an Actor, when in the loop, it can't receive any control messages (to change running)

Think of Actors as purely object oriented. All state is actually controlled by explicit message passing. So if you want to stop an actor's timer loop, you sent a stop message. Here is a very crude timer loop with stop:

using concurrent
const class Loopy : Actor
{
  new make(ActorPool p) : super(p) { sendLater(1sec, "loop") }
  Void stop() { send("stop") }
  override Obj? receive(Obj? msg)
  {
    echo("Receive $msg")
    if (msg == "stop") Actor.locals["stopped"] = "true"
    if (Actor.locals["stopped"] != "true") sendLater(1sec, "loop")
    return null
  }
  static Void main() 
  { 
    a := make(ActorPool())
    Actor.sleep(10sec)
    a.stop
    Actor.sleep(5sec)
  }  
}

SlimerDude Fri 24 Feb 2012

Hi Qualidafial,

Thanks! fwt::Desktop#callLater should work also. As the function would be executed in the same Thread, state would be shared. Though I'm currently thinking the stateful Actor above is probably more idiomatic as it doesn't rely on Fwt.

SlimerDude Fri 24 Feb 2012

Hi Brian, KevinKelley,

Cheers, I get it! You don't sleep in the receive method, instead you sendLater freeing up the Actor to receive stop messages.

Sweet!

Login or Signup to reply.