Merge branch '31-allow-engine-to-be-created-without-the-need-for-a-canvas' into 'master'

Resolve "Allow Engine to be created without the need for a canvas"

Closes #31

See merge request marty.oehme/cellular-automata!8
This commit is contained in:
Marty 2018-08-25 13:02:45 +00:00
commit 7d5b1cfe30
8 changed files with 137 additions and 34 deletions

View file

@ -1,8 +1,6 @@
import 'package:angular/angular.dart';
import 'package:angular_components/angular_components.dart';
import 'package:rules_of_living/service/configuration_service.dart';
import 'package:rules_of_living/service/engine_service.dart';
import 'package:rules_of_living/src/Engine.dart';
@Component(
selector: 'sim-controls',
@ -17,13 +15,12 @@ import 'package:rules_of_living/src/Engine.dart';
styleUrls: const ["controls_component.css"],
)
class ControlsComponent {
final EngineService engineService;
final EngineService engine;
Engine get engine => engineService.engine;
ControlsComponent(this.engineService);
ControlsComponent(this.engine);
void onStartClicked() {
engine.running = !engine.running;
engine.toggleRunning();
}
void onStepClicked() {
@ -35,8 +32,7 @@ class ControlsComponent {
}
void onRandomClicked() {
engine.running = false;
engine.addPattern();
engine.addRandomPattern();
}
void onClearClicked() {

View file

@ -1,7 +1,7 @@
<div id="controls">
<material-button id="reset" (click)="onResetClicked()"><material-icon icon="replay" baseline></material-icon></material-button>
<material-button id="run" (click)="onStartClicked()">
<span [ngSwitch]="engine.running">
<span [ngSwitch]="engine.isRunning">
<material-icon *ngSwitchCase="false" icon="play_arrow" baseline></material-icon>
<material-icon *ngSwitchCase="true" icon="pause" baseline></material-icon>
</span>

View file

@ -8,7 +8,6 @@ import 'package:rules_of_living/service/engine_service.dart';
templateUrl: "simulation_component.html",
directives: [coreDirectives],
providers: [],
// styleUrls: const ['package:angular_components/app_layout/layout.scss.css'],
)
class SimulationComponent implements OnInit {
final EngineService engineService;
@ -26,16 +25,10 @@ class SimulationComponent implements OnInit {
canvas.context2D.fillRect(0, 0, canvas.width, canvas.height);
canvas.context2D.setFillColorRgb(0, 255, 0);
canvas.context2D.fillText('''
If you see this\n
the app is broken :(
If you see this
the canvas did not load correctly :(
''', canvas.width / 2 - 50, canvas.height / 2);
engineService.create(canvas);
html.window.animationFrame.then(animFrame);
}
void animFrame(num now) {
engineService.engine.process(now);
html.window.animationFrame.then(animFrame);
engineService.canvas = canvas;
}
}

View file

@ -5,7 +5,45 @@ import 'package:rules_of_living/src/Engine.dart';
class EngineService {
Engine _engine;
Engine get engine => _engine;
Engine get engine => _engine ?? createEngine(null);
Engine createEngine(html.CanvasElement canvas) {
_engine = Engine(canvas);
return _engine;
}
void set canvas(html.CanvasElement canvas) => engine.canvas = canvas;
html.CanvasElement get canvas => engine.canvas;
void run() {
engine.running = true;
}
void stop() {
engine.running = false;
}
void toggleRunning() {
engine.running = !engine.running;
}
void step() {
engine.step();
}
void reset() {
engine.reset();
}
void addRandomPattern() {
engine.running = false;
engine.addPattern();
}
void clear() {
engine.clear();
}
bool get isRunning => engine.running;
void create(html.CanvasElement canvas) => _engine = Engine(canvas);
}

View file

@ -6,7 +6,7 @@ class Engine {
// Elapsed Time Counter - useful for Safety Timeout
Stopwatch _elapsed = new Stopwatch();
// Game Tick Rate - *does* impact game speed
// Game Tick Rate - *does* impact game speed TODO add configurable option
int _MS_PER_STEP = 1000 ~/ 3;
// Max Frame (i.e. Rendering) rate - does *not* impact game speed
@ -15,16 +15,31 @@ class Engine {
// ms stuck in updateloop after which game will declare itself unresponsive
final int SAFETY_TIMEOUT = 2000;
// Grid Size TODO add as configurable option
static final GRID_X = 100;
static final GRID_Y = 100;
num _updateLag = 0.0;
num _drawLag = 0.0;
final html.CanvasElement canvas;
Grid _grid = new Grid(100, 100);
bool _running = false;
/// 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;
Grid _grid = new Grid(GRID_X, GRID_Y);
bool running = false;
Engine(this.canvas) {
Engine([this.canvas]) {
_elapsed.start();
_grid.addPattern(amount: 15, dispersal: 5);
html.window.animationFrame.then(animFrame);
}
void animFrame(num now) {
process(now);
html.window.animationFrame.then(animFrame);
}
void reset() {
@ -33,7 +48,7 @@ class Engine {
}
void clear() {
_grid = new Grid(100, 100);
_grid = new Grid(GRID_X, GRID_Y);
running = false;
}
@ -58,19 +73,32 @@ class Engine {
}
}
/// 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() {
// print("updating");
if (!_grid.update()) 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;
_grid.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]) {
// print("rendering");
_grid.render(canvas, interp);
if(canvas != null) _grid.render(canvas, interp);
}
void addPattern(
@ -92,7 +120,4 @@ class Engine {
void toggleEdgeRendering() {
_grid.renderEdges = !_grid.renderEdges;
}
void set running(bool on) => _running = on;
bool get running => _running;
}

View file

@ -0,0 +1,19 @@
@TestOn('browser')
import 'package:test/test.dart';
import 'package:rules_of_living/service/engine_service.dart';
void main() {
EngineService sut;
setUp(() {
sut = EngineService();
});
test("EngineService creates an engine on demand", () {
expect(sut.engine, isNotNull);
});
test("EngineService returns the cached engine on subsequent requests", () {
expect(sut.engine, allOf(isNotNull, equals(sut.engine)));
});
}

32
test/src/engine_test.dart Normal file
View file

@ -0,0 +1,32 @@
import 'dart:html' as html;
@TestOn('browser')
import 'package:rules_of_living/src/Engine.dart';
import 'package:test/test.dart';
void main() {
Engine sut;
setUp(() {
sut = Engine();
});
test("Engine can be instantiated without canvas", () {
expect(sut, isNot(throwsNoSuchMethodError));
});
test("Engine does not throw errors when calling render directly", () {
// anonymous function necessary since throws can not use functions with args
expect(() => sut.render(), isNot(throwsNoSuchMethodError));
});
test("Engine does not throw errors when processing without attached canvas", () {
// anonymous function necessary since throws can not use functions with args
expect(() => sut.process(1000), isNot(throwsNoSuchMethodError));
});
test("setCanvas allows setting a canvas for an engine at any point", () {
sut.canvas = new html.CanvasElement();
expect(sut.canvas, isNotNull);
});
}