Skip to content

Instantly share code, notes, and snippets.

@mitsuoka
Last active July 13, 2024 18:27
Show Gist options
  • Save mitsuoka/2652622 to your computer and use it in GitHub Desktop.
Save mitsuoka/2652622 to your computer and use it in GitHub Desktop.
Dart code sample of a precision timer isolate.
/*
Dart code sample : Timer Isolate
Function timerIsolate provides your application with accurate timings with
+/- few mS accuracy depending on your pc's performance.
note : Windows Vista, Windows Server 2008, Windows 7 or later required for this code.
To try this sample:
1. Put these codes into the holder named TimerIsolateSample.
2. Create packages/browser directory in the TimerIsolateSample directory.
3. Place dart.js bootstrap code in the packages/browser directory.
4. Access TimerIsolateSample.html from Dartium like:
file:///C:/...../TimerIsolateSample/TimerIsolateSample.html
Messages:
To the timer:
1. "reset" : Command to reset the timer. Accepted at HOLD and RUN modes.
2. "start" : Command to start the timer. Accepted at IDLE and HOLD modes.
3. "hold" : Command to hold the timer. Accepted at RUN mode.
4. "?state" : Query to send the current mode.
5. "?elapsed" : Query to send the current elapsed time.
6. "?expiredFlags" : Query to send the current expired setting flags.
7. "?tripTimes" : Query to send the trip time settings.
8. {'tripTimes' : [camma separated trip times in mS]} : Set tripps. Accepted at IDLE mode. Causes implicit reset.
9. "tick" : Provisional tick to the timer isolate. Will be removed after the API revision.
From the timer:
1. "resetOk" : Acknowledgement to the reset command.
2. "startOk" : Acknowledgement to the start command.
3. "holdOk" : Acknowledgement to the hold command.
4. {'state' : int current_mode} : Current mode. Corresponds to "?state" query.
5. {'elapsed' : int elapsed_time_in_ms} : Corresponds to "?elapsed" query.
6. {'expiredFlags' : List<bool>} : Correspond to "?expiredFlags" query.
7. {'tripTimes' : List trip_times} : Correspons to "?tripTimes" query.
8. {'expired' : int timer_value} : Announcement that the timer has expired one of trip settings.
Tested on Dartium.
Refer to : www.cresc.co.jp/tech/java/Google_Dart/DartLanguageGuide.pdf (in Japanese)
April 2012, by Cresc Corp.
August 2012, modified to cope with API changes
October 2012, modified to incorporate M1 changes
January 2013, incorporated API changes
*/
library TimerIsolateLibrary;
import 'dart:async';
import 'dart:isolate' as isolate;
// long-lived ports
var _receivePort;
var _sendPort;
// timer modes
final IDLE = 0;
final RUN = 1;
final HOLD = 2;
// mode flag
var _mode = IDLE;
var _monitoring = false;
// variables
const int _tickMs = 20; // use 20 ms tick
List _tripTimes; // list of ms trip times
List _tripClocks; // list of trips in Stopwatch clocks
List _expiredFlags; // expired flags correspond to tripTimes
var _mainProcess; // main process object
Stopwatch _stopwatch; // instance of Stopwatch
Timer _timer; // instance of periodic timer
// top level timer isolate function
timerIsolate(){
// establish communication link
_receivePort = isolate.port;
Completer completer = new Completer();
Future linkEstablished = completer.future;
_receivePort.receive((msg, replyTo){
_sendPort = replyTo;
replyTo.send('hello', _receivePort.toSendPort());
completer.complete(true);
});
linkEstablished.then( (value) {
_mainProcess = new _MainProcess();
_mainProcess.run();
});
}
// *** main process class ***
class _MainProcess {
//local functions
void run() {
reset();
_receivePort.receive(processCommands);
Duration tick = const Duration(milliseconds: _tickMs);
// we still have hang-up problem for Timer.repeating in isolate when started from Dartium
/* _timer = new Timer.repeating(_tickMs, (Timer t){
if (_mode == RUN) periodicMonitor();
});
_sendPort.send('isolate: end of _MainProcess.run()'); */
}
int reportCurrentTimerValue(){
if (_mode == RUN || _mode == HOLD) { return (_stopwatch.elapsedMilliseconds);
} else { return 0;
}
}
void reset(){
_stopwatch = new Stopwatch();
_mode = IDLE;
// default for just in case
if (_tripTimes == null) _tripTimes = [500, 1000, 5000, 2000];
_tripTimes.sort((a, b){return (a - b);});
_expiredFlags = new List<bool>(_tripTimes.length);
_tripClocks = new List<int>(_tripTimes.length);
for(int i = 0; i < _expiredFlags.length; i++) _expiredFlags[i] = false;
for(int i = 0; i < _tripClocks.length; i++) _tripClocks[i] = _tripTimes[i] * 1000;
_sendPort.send('resetOk');
}
void start(){
if (_mode == HOLD || _mode == IDLE){
_stopwatch.start();
_mode = RUN;
_sendPort.send('startOk');
}
}
void hold(){
if (_mode == RUN){
_stopwatch.stop();
_mode = HOLD;
_sendPort.send('holdOk');
}
}
// periodic monitor
periodicMonitor(){
for(int i = 0; i < _tripTimes.length; i++) {
if (_tripClocks[i] < (_stopwatch.elapsedMicroseconds + _tickMs * 1000) && _expiredFlags[i] == false) {
do {} while (_tripClocks[i] >= _stopwatch.elapsedMicroseconds);
_expiredFlags[i] = true;
_sendPort.send({'expired' : _tripTimes[i]}); // report it immediately
}
else if ( _tripClocks[i] <= _stopwatch.elapsedMicroseconds && _expiredFlags[i] == false) {
_expiredFlags[i] = true;
_sendPort.send({'expired' : _tripTimes[i]}); // report it immediately
}
}
}
// command processor
processCommands(msg, replyTo){
if (msg is String){
switch(msg) {
case 'reset' :
reset();
break;
case 'start' :
start();
break;
case 'hold' :
hold();
break;
case '?state' :
_sendPort.send({'state':_mode});
break;
case '?elapsed' :
_sendPort.send({'elapsed':reportCurrentTimerValue()});
break;
case '?expiredFlags' :
_sendPort.send({'expiredFlags':_expiredFlags});
break;
case '?tripTimes' :
_sendPort.send({'tripTimes':_tripTimes});
break;
default :
if (_mode == RUN) _mainProcess.periodicMonitor();
}
}
else if (msg is Map){
if ((msg['tripTimes'] is List) && (_mode == IDLE)){
_tripTimes = msg['tripTimes'];
reset();
}
}
}
}
/*
Dart code sample to show how to use the timer isolate library
note : Following changes will be made in the near future:
1. Timer will be moved from dart:io to dart:core.
2. window.{set|clear}{Timeout|Interval} will be deprecated.
After that, use Timer instad ot window.setInterval such as:
new Timer.repeating(tickMs, tickFunction);
Tested on Dartium
April 2012, by Cresc corp.
October 2012, incorporated M1 changes
January 2013, incorporated API changes
Feburary 2013, API changes (Element.onClick.listen and DateTime) fixed
March 2013, API changes (Timer and String) fixed
*/
import 'dart:html';
import 'dart:async';
import 'dart:isolate' as isolate;
import 'TimerIsolateLibrary.dart';
// top level variables
// long-lived ports
var receivePort;
var sendPort;
// TimerController object
var timerController;
// spawn and start an isolate
class IsolateStarter {
void run(Function isolateFunction, Function nextStateFunction) {
// communication link establishment
Completer completer = new Completer();
Future linkEstablished = completer.future;
var isComplete = false;
sendPort = isolate.spawnFunction(isolateFunction);
log('spawned an isolate');
receivePort = new isolate.ReceivePort();
sendPort.send('hi', receivePort.toSendPort()); // tell the new send port
receivePort.receive((msg, replyTo){
log('initial state message received by parent : $msg');
if (! isComplete){
completer.complete(true);
isComplete = true;
};
});
linkEstablished.then(nextStateFunction);
log('communication link established');
}
}
// main class of this application
class TimerController {
List trips;
List expiredTrips;
int elapsedTimeCount = 0;
var state = IDLE; // counter mode
ButtonElement resetButton;
ButtonElement runButton;
ButtonElement holdButton;
void initializeTimer(){
resetButton = document.query("#b0");
runButton = document.query("#b1");
holdButton = document.query("#b2");
clearButtonColor();
resetButton.style.backgroundColor = "green";
trips = [500, 1500, 5000, 2500]; // set trips here
trips.sort((a,b){return (a - b);});
expiredTrips = new List<bool>(trips.length);
for(int i = 0; i < expiredTrips.length; i++) expiredTrips[i] = false;
writeTrips(trips, expiredTrips, 10);
sendPort.send({'tripTimes':trips});
}
void clearButtonColor(){
resetButton.style.backgroundColor = "white";
runButton.style.backgroundColor = "white";
holdButton.style.backgroundColor = "white";
}
// report processor
processReports(msg, replyTo){
if (msg is String) {log('received $msg');
} else if (msg is Map){
if (msg.containsKey('state')) state = msg['state'];
if (msg.containsKey('elapsed')) elapsedTimeCount = msg['elapsed'];
if (msg.containsKey('expiredFlags')) {
expiredTrips = msg['expiredFlags'];
writeTrips(trips, expiredTrips, 10);
}
if (msg.containsKey('tripTimes')) trips = msg['tripTimes'];
if (msg.containsKey('expired')) {
log('received ${msg["expired"]} mS expired message');
}
}
}
void setupButtonProcess(){
runButton.onClick.listen((e){
clearButtonColor();
runButton.style.backgroundColor = "red";
log("Run button clicked!");
sendPort.send('start');
});
holdButton.onClick.listen((e){
clearButtonColor();
holdButton.style.backgroundColor = "yellow";
log("Hold button clicked!");
sendPort.send('hold');
});
resetButton.onClick.listen((e){
clearButtonColor();
resetButton.style.backgroundColor = "green";
log("Reset button clicked!");
sendPort.send('reset');
});
}
void run(value){
initializeTimer();
receivePort.receive(processReports);
setupButtonProcess();
Duration tick1 = const Duration(milliseconds: 300); // use 0.3sec tick for display
new Timer.periodic(tick1, (timer){
sendPort.send('?elapsed');
sendPort.send('?expiredFlags');
writeCounter('Elapsed time : ${formatNumberBy3(elapsedTimeCount)} mS');
writeTrips(trips, expiredTrips, 10);
});
Duration tick2 = const Duration(milliseconds: 20); // use 20ms tick to the isolate
new Timer.periodic(tick2, (timer){
sendPort.send('tick');
});
}
}
// top level main function
void main() {
timerController = new TimerController();
new IsolateStarter().run(timerIsolate, timerController.run);
}
// functions for formatted output
void log(String msg) {
String timestamp = new DateTime.now().toString();
msg = '$timestamp : $msg';
print(msg);
document.query('#log').insertAdjacentHtml('beforeend', '$msg<br>');
}
void writeCounter(String message) {
document.query('#timerCount').innerHtml = message;
}
void writeTrips(List setting, List status, int len) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < setting.length; i++){
String s = formatNumberBy3(setting[i]);
String ss = '';
for(int j = 0; j < len-s.length; j++) ss = '${ss}\u00A0';
sb.write('$ss$s');
if (status[i]) sb.write(' : <font color="red">Expired</font>');
sb.write('<br>');
}
document.query('#trips').innerHtml = '$sb';
}
// function to format a number with separators. returns formatted number.
// original JS Author: Robert Hashemian (http://www.hashemian.com/)
// modified for Dart, 2012, by Cresc
// num - the number to be formatted
// decpoint - the decimal point character. if skipped, "." is used
// sep - the separator character. if skipped, "," is used
String formatNumberBy3(num number, {String decpoint: '.', String sep: ','}) {
// need a string for operations
String numstr = number.toString();
// separate the whole number and the fraction if possible
var a = numstr.split(decpoint);
var x = a[0]; // decimal
var y;
bool nfr = false; // no fraction flag
if (a.length == 1) { nfr = true;
} else { y = a[1];
} // fraction
var z = "";
var p = x.length;
if (p > 3) {
for (int i = p-1; i >= 0; i--) {
z = '$z${x[i]}';
if ((i > 0) && ((p-i) % 3 == 0) && (x[i-1].codeUnitAt(0) >= '0'.codeUnitAt(0))
&& (x[i-1].codeUnitAt(0) <= '9'.codeUnitAt(0))) { z = '$z,';
}
}
// reverse z to get back the number
x = '';
for (int i = z.length - 1; i>=0; i--) x = '$x${z[i]}';
}
// add the fraction back in, if it was there
if (nfr) return x; else return '$x$decpoint$y';
}
<!DOCTYPE html>
<html>
<head>
<title>TimerIsolateSample</title>
</head>
<body>
<h1 style="font:arial,sans-serif">Timer Isolate Sample</h1>
<h2 id="timerCount" font:arial,sans-serif>dart is not running</h2>
<div style="font:15px arial,sans-serif">
<button id="b0"; style="background-color:transparent; width: 100px"> reset </button><br>
<button id="b1"; style="background-color:transparent; width: 100px"> run </button><br>
<button id="b2"; style="background-color:transparent; width: 100px"> hold </button><br><br>
Trip settings (mS) : <span style="font-family: Courier New;"><p id="trips" ></p></span>
Log : <p id="log"></p>
</div>
<script type="application/dart" src="TimerIsolateSample.dart"></script>
<script src="packages/browser/dart.js"></script>
<!-- place the dart.js bootstrap code into the packages/burowser directory -->
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment