cellular-automata/lib/src/Engine.dart
Unknown de1aa46743 Separate Simulation calculating updates and merging
Simulation updates were one step of calculation and merging the calculations into the map in one function.

Separating the two allows checking for a new update without affecting the grid, allows passing the last Update around and allows custom changes to the grid by passing changes to the merge function that were not derived from the update function.
2018-10-18 10:58:06 +02:00

139 lines
4.2 KiB
Dart

import 'dart:html' as html;
import 'dart:math';
import 'package:rules_of_living/src/Simulation.dart';
class Engine {
// Elapsed Time Counter - useful for Safety Timeout
Stopwatch _elapsed = new Stopwatch();
/// Game Tick Rate
///
/// *does* impact game speed; dictates how long each logic step takes. Only
/// interesting for engine internal calculations, to set simulation speed
/// from the outside use stepsPerSecond instead.
int _MS_PER_STEP = 1000 ~/ 3;
/// Set Logic Updates per Second
///
/// Dictates simulation speed. Sets the amount of (logic) updates per second.
/// Translations between number of updates and their timings is not exact so
/// comparing this variable to fixed ints might not yield the expected results.
/// Does not affect render frequency, which is handled by framesPerSecond.
int get stepsPerSecond => 1000 ~/ _MS_PER_STEP;
set stepsPerSecond(int val) => _MS_PER_STEP = 1000 ~/ val;
// Max Frame (i.e. Rendering) rate - does *not* impact game speed
final int _MS_PER_FRAME = 1000 ~/ 30;
// ms stuck in updateloop after which game will declare itself unresponsive
final int SAFETY_TIMEOUT = 2000;
/// Grid Size
///
/// Number of cells on x coordinate and y coordinate. Can be set individually.
Point get gridSize => Point<int>(_simulation.w, _simulation.h);
void set gridSize(Point<int> value) {
if (value.x <= 0 || value.y <= 0)
throw ArgumentError("grid size must not be smaller than 1");
_simulation = Simulation(value.x, value.y);
}
num _updateLag = 0.0;
num _drawLag = 0.0;
/// The Canvas to Paint on
///
/// Manually define or change the canvas the engine should render to. Should
/// be used if no canvas was defined at engine creation and it should be
/// rendered later.
html.CanvasElement canvas;
Simulation _simulation;
bool running = false;
Engine([x = 100, y = 100, this.canvas]) {
_simulation = Simulation(x, y);
_elapsed.start();
_simulation.addPattern(amount: 15, dispersal: 5);
html.window.animationFrame.then(animFrame);
}
void animFrame(num now) {
process(now);
html.window.animationFrame.then(animFrame);
}
void reset() {
_simulation.reset();
running = false;
}
void clear() {
_simulation = new Simulation(gridSize.x, gridSize.y);
running = false;
}
void process(num now) {
_drawLag += _elapsed.elapsedMilliseconds;
_updateLag += _elapsed.elapsedMilliseconds;
_elapsed.reset();
while (_updateLag >= _MS_PER_STEP) {
if (_elapsed.elapsedMilliseconds > SAFETY_TIMEOUT) {
// TODO stub - give warning etc when this occurs
print("ERROR STUCK IN UPDATE LOOP");
break;
}
if (running == true) update();
_updateLag -= _MS_PER_STEP;
}
if (_drawLag >= _MS_PER_FRAME) {
render(_updateLag / _MS_PER_STEP);
_drawLag = 0;
}
}
/// Update Engine Logic
///
/// Updates the logic of the engine by one tick. Should usually not be called
/// directly, since it is automatically taken care of by the processing function.
/// If simulation should be advanced manually one time, prefer using step().
void update() {
// TODO: create hasUpdated/hasAdvanced method in simulation to abstract actual updating away
Map<int, bool> simulationUpdate = _simulation.update();
_simulation.mergeStateChanges(simulationUpdate);
if (simulationUpdate.length == 0) running = false;
}
/// Advances Logic One Update
///
/// Moves logic of the engine one step forward and pauses the
/// simulation. Does not automatically re-render the new state
/// (though this should usually not pose a problem).
void step() {
running = false;
_simulation.update();
}
/// Renders the Current Simulation State
///
/// Renders the simulation once. Will usually automatically be called by
/// the internal engine processing. Does not do anything if no canvas is
/// defined.
void render([num interp]) {
if (canvas == null) return;
_simulation.render(canvas, interp);
}
void addPattern({int amount, int dispersal}) {
_simulation.addPattern(amount: amount, dispersal: dispersal);
}
void toggleEdgeRendering() {
_simulation.renderEdges = !_simulation.renderEdges;
}
}