Refactor Engine Methods

Extract method checking for update necessity.
This commit is contained in:
Unknown 2018-10-19 11:50:34 +02:00
parent 2993b33d9e
commit e8c1e6ed8b
2 changed files with 112 additions and 16 deletions

View file

@ -1,5 +1,4 @@
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:math';
import 'package:rules_of_living/src/Simulation.dart'; import 'package:rules_of_living/src/Simulation.dart';
@ -47,31 +46,37 @@ class Engine {
} }
void animFrame(num now) { void animFrame(num now) {
process(now); int elapsed = _elapsed.elapsedMilliseconds;
_elapsed.reset();
process(elapsed, SAFETY_TIMEOUT, update: this.update, render: this.render);
html.window.animationFrame.then(animFrame); html.window.animationFrame.then(animFrame);
} }
void process(num now) { void process(int elapsed, int timeOut, {Function update, Function render}) {
_drawLag += _elapsed.elapsedMilliseconds; _drawLag += elapsed;
_updateLag += _elapsed.elapsedMilliseconds; _updateLag += elapsed;
_elapsed.reset();
while (_updateLag >= _MS_PER_STEP) { while (running == true &&
if (_elapsed.elapsedMilliseconds > SAFETY_TIMEOUT) { _shouldUpdate(_updateLag, elapsed, timeOut) == true) {
// TODO stub - give warning etc when this occurs
print("ERROR STUCK IN UPDATE LOOP");
break;
}
if (running == true) update();
_updateLag -= _MS_PER_STEP; _updateLag -= _MS_PER_STEP;
if (update == null) break;
update();
} }
if (_drawLag >= _MS_PER_FRAME) { if (_drawLag >= _MS_PER_FRAME) {
render(_updateLag / _MS_PER_STEP);
_drawLag = 0; _drawLag = 0;
if (render == null) return;
render(_updateLag / _MS_PER_STEP);
} }
} }
bool _shouldUpdate(int updateLag, int elapsed, int timeOut) {
if (updateLag < _MS_PER_STEP) return false;
if (elapsed > timeOut) throw StackOverflowError;
return true;
}
/// Update Engine Logic /// Update Engine Logic
/// ///
/// Updates the logic of the engine by one tick. Should usually not be called /// Updates the logic of the engine by one tick. Should usually not be called

View file

@ -1,15 +1,106 @@
import 'dart:html' as html; import 'dart:html' as html;
import 'dart:math'; import 'package:rules_of_living/src/Simulation.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
@TestOn('browser') @TestOn('browser')
import 'package:rules_of_living/src/Engine.dart'; import 'package:rules_of_living/src/Engine.dart';
import 'package:test/test.dart';
class MockSimulation extends Mock implements Simulation {
int updateNum = 0;
bool hasChanges = false;
@override
Map<int, bool> update() {
updateNum++;
return hasChanges ? {1: true, 2: false} : {};
}
}
void main() { void main() {
Engine sut; Engine sut;
setUp(() { setUp(() {
sut = Engine(); sut = Engine();
}); });
group("process", () {
setUp(() => sut.running = true);
test("errors out if updating takes too long",
() => expect(() => sut.process(5000, 10), throwsA(StackOverflowError)));
test("does not update if not enough time elapsed to pass ms per step", () {
bool result = false;
sut.stepsPerSecond = 1000;
sut.process(999, 2000,
update: () => result = true, render: (double interp) => null);
expect(result, true);
});
test("updates only when the ms per step threshold is crossed", () {
int updateNum = 0;
sut.stepsPerSecond = 1;
sut.process(1001, 2000, update: () => updateNum++);
expect(updateNum, equals(1));
});
test("updates until updateLag has been resolved", () {
int updateNum = 0;
sut.stepsPerSecond = 1;
sut.process(2999, 5000, update: () => updateNum++);
expect(updateNum, equals(2));
});
test("works without passing in update or render function arguments", () {
sut.stepsPerSecond = 1000;
expect(() => sut.process(500, 5000), isNot(throwsA(anything)));
});
});
group("update", () {
MockSimulation mockSim;
setUp(() {
mockSim = MockSimulation();
sut.simulation = mockSim;
});
test("does not error out if no simulation variable is set", () {
sut.simulation = null;
expect(() => sut.update(), isNot(throwsA(anything)));
});
test("updates simulation one tick for every time it is called", () {
sut.update();
sut.update();
sut.update();
expect(mockSim.updateNum, equals(3));
});
test("sets running to false when simulation returns no changes", () {
sut.running = true;
sut.update();
expect(sut.running, equals(false));
});
test("keeps running when simulation returns changes", () {
sut.running = true;
mockSim.hasChanges = true;
sut.update();
expect(sut.running, equals(true));
});
});
group("step", () {
MockSimulation mockSim;
setUp(() {
mockSim = MockSimulation();
sut.simulation = mockSim;
});
test("advances the simulation by one update", () {
sut.step();
expect(mockSim.updateNum, equals(1));
});
test("turns off continuous engine updates", () {
sut.running = true;
sut.step();
expect(sut.running, equals(false));
});
});
group("canvas", () { group("canvas", () {
test("Engine can be instantiated without canvas", () { test("Engine can be instantiated without canvas", () {
expect(sut, isNot(throwsNoSuchMethodError)); expect(sut, isNot(throwsNoSuchMethodError));