Last active
July 13, 2024 18:27
-
-
Save mitsuoka/2652622 to your computer and use it in GitHub Desktop.
Dart code sample of a precision timer isolate.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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'; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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