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(_simulation.w, _simulation.h); void set gridSize(Point 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 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; } }