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:
commit
7d5b1cfe30
8 changed files with 137 additions and 34 deletions
|
@ -1,8 +1,6 @@
|
||||||
import 'package:angular/angular.dart';
|
import 'package:angular/angular.dart';
|
||||||
import 'package:angular_components/angular_components.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/service/engine_service.dart';
|
||||||
import 'package:rules_of_living/src/Engine.dart';
|
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
selector: 'sim-controls',
|
selector: 'sim-controls',
|
||||||
|
@ -17,13 +15,12 @@ import 'package:rules_of_living/src/Engine.dart';
|
||||||
styleUrls: const ["controls_component.css"],
|
styleUrls: const ["controls_component.css"],
|
||||||
)
|
)
|
||||||
class ControlsComponent {
|
class ControlsComponent {
|
||||||
final EngineService engineService;
|
final EngineService engine;
|
||||||
|
|
||||||
Engine get engine => engineService.engine;
|
ControlsComponent(this.engine);
|
||||||
ControlsComponent(this.engineService);
|
|
||||||
|
|
||||||
void onStartClicked() {
|
void onStartClicked() {
|
||||||
engine.running = !engine.running;
|
engine.toggleRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStepClicked() {
|
void onStepClicked() {
|
||||||
|
@ -35,8 +32,7 @@ class ControlsComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRandomClicked() {
|
void onRandomClicked() {
|
||||||
engine.running = false;
|
engine.addRandomPattern();
|
||||||
engine.addPattern();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onClearClicked() {
|
void onClearClicked() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<material-button id="reset" (click)="onResetClicked()"><material-icon icon="replay" baseline></material-icon></material-button>
|
<material-button id="reset" (click)="onResetClicked()"><material-icon icon="replay" baseline></material-icon></material-button>
|
||||||
<material-button id="run" (click)="onStartClicked()">
|
<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="false" icon="play_arrow" baseline></material-icon>
|
||||||
<material-icon *ngSwitchCase="true" icon="pause" baseline></material-icon>
|
<material-icon *ngSwitchCase="true" icon="pause" baseline></material-icon>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:rules_of_living/service/engine_service.dart';
|
||||||
templateUrl: "simulation_component.html",
|
templateUrl: "simulation_component.html",
|
||||||
directives: [coreDirectives],
|
directives: [coreDirectives],
|
||||||
providers: [],
|
providers: [],
|
||||||
// styleUrls: const ['package:angular_components/app_layout/layout.scss.css'],
|
|
||||||
)
|
)
|
||||||
class SimulationComponent implements OnInit {
|
class SimulationComponent implements OnInit {
|
||||||
final EngineService engineService;
|
final EngineService engineService;
|
||||||
|
@ -26,16 +25,10 @@ class SimulationComponent implements OnInit {
|
||||||
canvas.context2D.fillRect(0, 0, canvas.width, canvas.height);
|
canvas.context2D.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
canvas.context2D.setFillColorRgb(0, 255, 0);
|
canvas.context2D.setFillColorRgb(0, 255, 0);
|
||||||
canvas.context2D.fillText('''
|
canvas.context2D.fillText('''
|
||||||
If you see this\n
|
If you see this
|
||||||
the app is broken :(
|
|
||||||
|
the canvas did not load correctly :(
|
||||||
''', canvas.width / 2 - 50, canvas.height / 2);
|
''', canvas.width / 2 - 50, canvas.height / 2);
|
||||||
engineService.create(canvas);
|
engineService.canvas = canvas;
|
||||||
|
|
||||||
html.window.animationFrame.then(animFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
void animFrame(num now) {
|
|
||||||
engineService.engine.process(now);
|
|
||||||
html.window.animationFrame.then(animFrame);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,45 @@ import 'package:rules_of_living/src/Engine.dart';
|
||||||
class EngineService {
|
class EngineService {
|
||||||
Engine _engine;
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Engine {
|
||||||
// Elapsed Time Counter - useful for Safety Timeout
|
// Elapsed Time Counter - useful for Safety Timeout
|
||||||
Stopwatch _elapsed = new Stopwatch();
|
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;
|
int _MS_PER_STEP = 1000 ~/ 3;
|
||||||
|
|
||||||
// Max Frame (i.e. Rendering) rate - does *not* impact game speed
|
// 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
|
// ms stuck in updateloop after which game will declare itself unresponsive
|
||||||
final int SAFETY_TIMEOUT = 2000;
|
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 _updateLag = 0.0;
|
||||||
num _drawLag = 0.0;
|
num _drawLag = 0.0;
|
||||||
|
|
||||||
final html.CanvasElement canvas;
|
/// The Canvas to Paint on
|
||||||
Grid _grid = new Grid(100, 100);
|
///
|
||||||
bool _running = false;
|
/// 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();
|
_elapsed.start();
|
||||||
_grid.addPattern(amount: 15, dispersal: 5);
|
_grid.addPattern(amount: 15, dispersal: 5);
|
||||||
|
html.window.animationFrame.then(animFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void animFrame(num now) {
|
||||||
|
process(now);
|
||||||
|
html.window.animationFrame.then(animFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
|
@ -33,7 +48,7 @@ class Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_grid = new Grid(100, 100);
|
_grid = new Grid(GRID_X, GRID_Y);
|
||||||
running = false;
|
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() {
|
void update() {
|
||||||
// print("updating");
|
|
||||||
if (!_grid.update()) running = false;
|
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() {
|
void step() {
|
||||||
running = false;
|
running = false;
|
||||||
_grid.update();
|
_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]) {
|
void render([num interp]) {
|
||||||
// print("rendering");
|
if(canvas != null) _grid.render(canvas, interp);
|
||||||
_grid.render(canvas, interp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPattern(
|
void addPattern(
|
||||||
|
@ -92,7 +120,4 @@ class Engine {
|
||||||
void toggleEdgeRendering() {
|
void toggleEdgeRendering() {
|
||||||
_grid.renderEdges = !_grid.renderEdges;
|
_grid.renderEdges = !_grid.renderEdges;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set running(bool on) => _running = on;
|
|
||||||
bool get running => _running;
|
|
||||||
}
|
}
|
||||||
|
|
19
test/service/engine_service_test.dart
Normal file
19
test/service/engine_service_test.dart
Normal 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
32
test/src/engine_test.dart
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue