3

I am stuck trying to do something simple. I want to be able to count upwards until I click on the screen at which point i want the counting to stop. In reality the code itself will be carrying out complex AI calculations for a game, but first I want to understand how to control the while loop. In android this would be trivial.

Here is what my code looks like

bool OK = true;

main() async{
 html.querySelector('#content').onMouseUp.listen((e){
   OK = false;
 });

 await for(int i in naturals){
   print(i);
   await sleep();
 }
}

Stream get naturals async* {
 int k = 0; while (OK) { yield await k++; }
}

Future sleep() {
 return new Future.delayed(const Duration(milliseconds: 1), () => "1");
}

I put the sleep() method in as a way to ensure that control is passed to the event loop.

Is it possible to control a while loop without the sleep() method?

Stephen Poole
  • 73
  • 1
  • 5
  • Why do you wan't to count in a loop? Could you not just measure the time when you "start counting" until the click happened? – Günter Zöchbauer Apr 21 '16 at 19:31
  • I want to be able to carry out some chunks of processing in a while loop - the task will be searching for words in a stack of random letters. This needs to stop when the opponent moves so i want to be able to change the value of OK from true to false at that moment. – Stephen Poole Apr 21 '16 at 19:59
  • I still have no idea what you mean by "control a while loop". Your generator function makes your code event-driven anyway. `await sleep()` adds some additional cycles for the browser main thread. I don't know if this is useful/necessary. – Günter Zöchbauer Apr 21 '16 at 20:15
  • I simply mean I want to be able to change the bool from true to false by using a mouseEvent to stop the while loop. Without the sleep() the execution is locked in the while loop so the mouse event is never picked up. In Java the OK would be a static variable that could be changed from another thread. – Stephen Poole Apr 21 '16 at 22:16

3 Answers3

2

update

Just enqueue to allow the event queue to process outstanding events without additional delay use

Future sleep() {
  return new Future.delayed(Duration.ZERO);
}

original

JavaScript and therefore can't Dart have threads in the browser and you need to pass control back to the event queue to allow it to process other events like you do with sleep().

Another approach would be to use webworkers and run the heavy calculation in a background task (webworker) to keep the UI thread free to process user events. This way even additional CPU cores can be utilized which isn't possible when all code runs in the browsers UI thread.

I don't know how to create webworkers in Dart though except with Angular2 Dart Web workers in Angular 2 Dart where Angular does the initialization. I don't think it's too difficult but I don't know any docs.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Thank you Gunter. I appreciate the comment. I can get a web worker working, but the exact same problem exists: in HTML only spawnuri can work so we can only control the worker with messages. The while loop in the worker prevents messages in worker from being actioned so the loop can't be stopped. It would be nice to have a sleep function for one clock cycle as a millisecond seems very long. – Stephen Poole Apr 22 '16 at 15:51
  • I guess new `new Future.delayed.const(Duration.ZERO), (){})` or even `new Future.value(null)` might do what you want. That should just enqueue into the message queue without any additional delay to be processed when previously enqueued tasks are done. https://www.dartlang.org/articles/event-loop/ should provide more information about this topic. There are different queues (event, microtask) and completed Futures are again treated differently AFAIR. – Günter Zöchbauer Apr 22 '16 at 16:08
  • Ok, I checked it. `new Future.delayed.const(Duration.ZERO))` works fine but `new Future.value()` doesn't. – Günter Zöchbauer Apr 22 '16 at 16:33
  • 1
    Yes, that works Gunter. Thank you very much for your help. – Stephen Poole Apr 22 '16 at 16:35
2

To provide a more general answer - instead of a loop, you want to schedule a sequence of future tasks that each executes one iteration or step of your AI code (or whatever background process you want to have running).

You can have the step task recursively schedule itself:

dynamic doSomething(_) {
  print('Doing something ...'); 
  if(!stop) {
    new Future.delayed(delay, (){}).then(doSomething);
  }
  return null;
}

main() async {

  doSomething(null);

}

Although I don't recommend doing this. It's awkward to control - the step code has to check a flag variable to see if it should continue or stop and it's free running.

Alternatively you could use a Timer:

void doSomething(Timer timer) {
  print('Doing something ...');  
}

main() async {

 new Timer.periodic(delay, doSomething);

}

This is throttled at a constant rate and has a uniform time step, and is easier to stop (call cancel() on the timer).

Another approach might be to synchronize with the browser's draw refresh cycle by requesting future animation frames:

import 'dart:html';

doSomething(num delta) {
  print('Doing something ...');  
  window.animationFrame.then(doSomething);
}

void main() {
  window.animationFrame.then(doSomething);
}

Time steps are not constant but you get the time delta. The advantage of this approach is that animation frame futures will not be scheduled if the browser window is hidden.

See How do I drive an animation loop at 60fps with Dart?

Those are very simple examples. Setting up proper background processes for physics simulation and AI in web games is actually surprisingly (to me at least) non-trivial. Here are two resources I found helpful.

http://gameprogrammingpatterns.com/ - a nice free online book of game programming patterns. http://gameprogrammingpatterns.com/game-loop.html - chapter on game loops.

http://gafferongames.com/game-physics/fix-your-timestep/ - part of a sequence of articles on physics simulation in games.

Argenti Apparatus
  • 3,857
  • 2
  • 25
  • 35
1

Following all suggestions this is the code I have ended up with, putting the heavy lifting in a worker, with controllable stopping and starting. I have used simple counting to get this working, but this is being replaced with my complex AI game calculations.

This uses time slices of 100ms within a worker isolate, which can only be interrupted after it has finished the batch. This allows me complete freedom to have complex animations on the screen while all this hard work is being done.

import 'dart:html' as html;
import 'dart:isolate';
import 'dart:math';

SendPort worker;
bool working = false;

main() async{

  await startWorker();

  html.querySelector('#stage').onMouseUp.listen((e){
    if(working)worker.send('stop');
    else worker.send('go');
  });

}

startWorker() async{
  var response = new ReceivePort();

  await Isolate.spawnUri(Uri.parse("worker.dart"), null ,response.sendPort)
      .then((_) => response.listen((msg){

    if(msg is SendPort) {
      worker = msg;
    }
    else {
      messageReceived(msg);
    }
  }));
}

messageReceived(String message){
  switch (message){
    case 'working': working = true;
    break;

    case 'idle': working = false;
      break;

    default : print(message);
      break;
  }
}

worker.dart

import 'dart:isolate';
import 'dart:async';

SendPort replyTo;
bool OK = false;
const timeSlice = 100;

main(List<String> args, SendPort reply) async{
  var response = new ReceivePort();
  reply.send(response.sendPort);
  replyTo = reply;
  response.listen((msg) => messageReceived(msg));
  replyTo.send("Hello from worker. Click to start and stop me");
}

messageReceived(String message){
  switch(message){
    case 'stop':
      replyTo.send('idle');
      OK = false;
      break;

    case 'go':
      replyTo.send('working');
      go();
      break;
  }
}

go()async {
  OK = true;
  int i = 0;

  batchJob(){
    int startTime = new DateTime.now().millisecondsSinceEpoch;
    int elapsed = 0;

    while(elapsed < timeSlice){

      i ++; // the central routine

      elapsed = new DateTime.now().millisecondsSinceEpoch - startTime;
    }
 }

  while(OK){
    batchJob();
    replyTo.send(i.toString());
    await new Future.delayed(const Duration(milliseconds: 0), () {});
  }
}
Stephen Poole
  • 73
  • 1
  • 5