├── .pubignore
├── watch.cmd
├── .gitignore
├── test
├── all_tests.dart
└── dartemis
│ └── core
│ ├── systems
│ ├── test_systems.dart
│ └── interval_entity_system_test.dart
│ ├── utils
│ ├── all_tests.dart
│ ├── bit_set_test.dart
│ ├── bag_test.dart
│ └── entity_bag_test.dart
│ ├── managers
│ ├── test_managers.dart
│ ├── group_manager_test.dart
│ └── tag_manager_test.dart
│ ├── entity_manager_test.dart
│ ├── all_tests.dart
│ ├── mapper_test.dart
│ ├── component_test.dart
│ ├── components_setup.dart
│ ├── aspect_test.dart
│ ├── component_manager_test.dart
│ ├── world_test.mocks.dart
│ └── world_test.dart
├── lib
├── src
│ ├── core
│ │ ├── component_type.dart
│ │ ├── entity.dart
│ │ ├── entity_observer.dart
│ │ ├── systems
│ │ │ ├── entity_processing_system.dart
│ │ │ ├── void_entity_system.dart
│ │ │ ├── interval_entity_processing_system.dart
│ │ │ └── interval_entity_system.dart
│ │ ├── manager.dart
│ │ ├── component.dart
│ │ ├── utils
│ │ │ ├── entity_bag.dart
│ │ │ ├── object_pool.dart
│ │ │ ├── bag.dart
│ │ │ └── bit_set.dart
│ │ ├── managers
│ │ │ ├── team_manager.dart
│ │ │ ├── player_manager.dart
│ │ │ ├── tag_manager.dart
│ │ │ └── group_manager.dart
│ │ ├── mapper.dart
│ │ ├── entity_manager.dart
│ │ ├── aspect.dart
│ │ ├── entity_system.dart
│ │ ├── component_manager.dart
│ │ └── world.dart
│ └── metadata
│ │ └── generate.dart
└── dartemis.dart
├── example
├── styles.css
├── index.html
├── darteroids
│ ├── components.dart
│ ├── input_systems.dart
│ ├── render_systems.dart
│ └── gamelogic_systems.dart
└── main.dart
├── .github
├── dependabot.yml
└── workflows
│ └── dart.yml
├── pubspec.yaml
├── LICENSE
├── README.md
├── analysis_options.yaml
└── CHANGELOG.md
/.pubignore:
--------------------------------------------------------------------------------
1 | watch.cmd
--------------------------------------------------------------------------------
/watch.cmd:
--------------------------------------------------------------------------------
1 | dart run build_runner watch
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .dart_tool/
2 | pubspec.lock
3 | build/
4 | .idea/
5 | .packages
6 | /.dart_tool/
7 |
--------------------------------------------------------------------------------
/test/all_tests.dart:
--------------------------------------------------------------------------------
1 | import 'dartemis/core/all_tests.dart' as core;
2 |
3 | void main() {
4 | core.main();
5 | }
6 |
--------------------------------------------------------------------------------
/test/dartemis/core/systems/test_systems.dart:
--------------------------------------------------------------------------------
1 | import 'interval_entity_system_test.dart' as interval_entity_system;
2 |
3 | void main() {
4 | interval_entity_system.main();
5 | }
6 |
--------------------------------------------------------------------------------
/test/dartemis/core/utils/all_tests.dart:
--------------------------------------------------------------------------------
1 | import 'bag_test.dart' as bag;
2 | import 'entity_bag_test.dart' as entity_bag;
3 |
4 | void main() {
5 | bag.main();
6 | entity_bag.main();
7 | }
8 |
--------------------------------------------------------------------------------
/lib/src/core/component_type.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// The [ComponentType] handles the internal id and bitmask of a [Component].
4 | extension type ComponentType(int bitIndex) {}
5 |
--------------------------------------------------------------------------------
/example/styles.css:
--------------------------------------------------------------------------------
1 | canvas {
2 | border: solid 1px black;
3 | user-select: none;
4 | }
5 |
6 | div#canvascontainer {
7 | text-align: center;
8 | }
9 |
10 | h1 {
11 | text-align: center;
12 | }
13 |
--------------------------------------------------------------------------------
/test/dartemis/core/managers/test_managers.dart:
--------------------------------------------------------------------------------
1 | import 'group_manager_test.dart' as group_manager;
2 | import 'tag_manager_test.dart' as tag_manager;
3 |
4 | void main() {
5 | group_manager.main();
6 | tag_manager.main();
7 | }
8 |
--------------------------------------------------------------------------------
/lib/src/core/entity.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// The Entity type. Cannot be instantiated outside of dartemis. You must
4 | /// create new entities using [World.createEntity].
5 | extension type Entity(int _id) {
6 | Entity._(this._id);
7 | }
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Dependabot configuration file.
2 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
3 |
4 | version: 2
5 |
6 | updates:
7 | - package-ecosystem: github-actions
8 | directory: /
9 | schedule:
10 | interval: monthly
11 | labels:
12 | - autosubmit
13 | groups:
14 | github-actions:
15 | patterns:
16 | - "*"
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: dartemis
2 | version: 0.10.0
3 | description: An Entity System Framework for game development. Based on Artemis.
4 | repository: https://github.com/denniskaselow/dartemis
5 | environment:
6 | sdk: ^3.3.0
7 |
8 | dependencies:
9 | meta: ^1.16.0
10 |
11 | dev_dependencies:
12 | build_runner: ^2.0.0
13 | build_web_compilers: ^4.0.0
14 | mockito: ^5.5.0
15 | test: ^1.23.0
16 | web: ^1.1.0
17 |
--------------------------------------------------------------------------------
/lib/src/core/entity_observer.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// Interface for [EntitySystem]s and [Manager]s to get informed about changes
4 | /// to the state of an [int].
5 | abstract class EntityObserver {
6 | /// Called when an [entity] is added to the world.
7 | @visibleForOverriding
8 | void added(Entity entity);
9 |
10 | /// Called when an [entity] is being deleted from the world.
11 | @visibleForOverriding
12 | void deleted(Entity entity);
13 | }
14 |
--------------------------------------------------------------------------------
/test/dartemis/core/entity_manager_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('integration tests for EntityManager', () {
6 | late World world;
7 | setUp(() {
8 | world = World();
9 | });
10 | test('entities have uniqure IDs', () {
11 | final a = world.createEntity();
12 | final b = world.createEntity();
13 |
14 | expect(a, isNot(equals(b)));
15 | });
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Darteroids
8 |
9 |
10 |
11 |
12 | Darteroids
13 |
14 |
15 |
Move the red circle with WASD-keys and shoot with the left mouse button
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/dartemis/core/all_tests.dart:
--------------------------------------------------------------------------------
1 | import 'aspect_test.dart' as aspect;
2 | import 'component_manager_test.dart' as component_manager;
3 | import 'component_test.dart' as component;
4 | import 'entity_manager_test.dart' as entity_manager;
5 | import 'managers/test_managers.dart' as managers;
6 | import 'mapper_test.dart' as mapper;
7 | import 'systems/test_systems.dart' as systems;
8 | import 'utils/all_tests.dart' as utils;
9 | import 'world_test.dart' as world;
10 |
11 | void main() {
12 | aspect.main();
13 | component.main();
14 | component_manager.main();
15 | entity_manager.main();
16 | mapper.main();
17 |
18 | managers.main();
19 | systems.main();
20 | utils.main();
21 |
22 | world.main();
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/core/systems/entity_processing_system.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// A typical entity system. Use this when you need to process entities
4 | /// possessing the provided component types.
5 | abstract class EntityProcessingSystem extends EntitySystem {
6 | /// Create a new [EntityProcessingSystem]. It requires at least one component.
7 | EntityProcessingSystem(super.aspect, {super.group, super.passive});
8 |
9 | /// Process an [entity] this system is interested in.
10 | @visibleForOverriding
11 | void processEntity(Entity entity);
12 |
13 | @override
14 | @visibleForOverriding
15 | void processEntities(Iterable entities) =>
16 | entities.forEach(processEntity);
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/core/systems/void_entity_system.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// This system has an empty aspect so it processes no entities, but it still
4 | /// gets invoked.
5 | /// You can use this system if you need to execute some game logic and not have
6 | /// to concern yourself about aspects or entities.
7 | abstract class VoidEntitySystem extends EntitySystem {
8 | /// Create the [VoidEntitySystem].
9 | VoidEntitySystem({super.group, super.passive}) : super(Aspect());
10 |
11 | @override
12 | @visibleForOverriding
13 | void processEntities(Iterable entities) => processSystem();
14 |
15 | /// Execute the logic for this system.
16 | @visibleForOverriding
17 | void processSystem();
18 | }
19 |
--------------------------------------------------------------------------------
/test/dartemis/core/utils/bit_set_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/src/core/utils/bit_set.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('BitSet', () {
6 | test('toIntValues() works in general', () {
7 | final sut = BitSet(32)..setAll();
8 |
9 | expect(sut.toIntValues(), equals(List.generate(32, (index) => index)));
10 | });
11 | test('toIntValues() works for edge cases', () {
12 | final sut = BitSet(128);
13 | sut[0] = true;
14 | sut[31] = true;
15 | sut[32] = true;
16 | sut[63] = true;
17 | sut[64] = true;
18 | sut[127] = true;
19 |
20 | expect(sut.toIntValues(), equals([0, 31, 32, 63, 64, 127]));
21 | });
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/core/manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// Manager.
4 | abstract class Manager implements EntityObserver {
5 | late final World _world;
6 |
7 | /// The [World] where this manager resides.
8 | World get world => _world;
9 |
10 | /// Override to implement code that gets executed when managers are
11 | /// initialized.
12 | @mustCallSuper
13 | @visibleForOverriding
14 | // ignore: use_setters_to_change_properties
15 | void initialize(World world) {
16 | _world = world;
17 | }
18 |
19 | @override
20 | @visibleForOverriding
21 | void added(Entity entity) {}
22 |
23 | @override
24 | @visibleForOverriding
25 | void deleted(Entity entity) {}
26 |
27 | /// Called when the world gets destroyed. Override if you need to clean up
28 | /// your manager.
29 | @visibleForOverriding
30 | void destroy() {}
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/core/systems/interval_entity_processing_system.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// If you need to process entities at a certain interval then use this.
4 | /// A typical usage would be to regenerate ammo or health at certain intervals,
5 | /// no need to do that every game loop, but perhaps every 100 ms. or every
6 | /// second.
7 | abstract class IntervalEntityProcessingSystem extends IntervalEntitySystem {
8 | /// Create a new [IntervalEntityProcessingSystem]. It requires at least one
9 | /// component.
10 | IntervalEntityProcessingSystem(
11 | super.interval,
12 | super.aspect, {
13 | super.group,
14 | super.passive,
15 | });
16 |
17 | /// Process an [entity] this system is interested in.
18 | @visibleForOverriding
19 | void processEntity(Entity entity);
20 |
21 | @override
22 | @visibleForOverriding
23 | void processEntities(Iterable entities) =>
24 | entities.forEach(processEntity);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/core/component.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// All components extend from this class.
4 | ///
5 | /// If you want to use a pooled component that will be added to a FreeList when
6 | /// it is being removed use [PooledComponent] instead.
7 | abstract class Component {
8 | /// Does nothing in [Component], only relevant for [PooledComponent].
9 | void _removed() {}
10 | }
11 |
12 | /// All components that should be managed in a [ObjectPool] must extend this
13 | /// class and have a factory constructor that calls `Pooled.of(...)` to create
14 | /// a component. By doing so, dartemis can handle the construction of
15 | /// [PooledComponent]s and reuse them when they are no longer needed.
16 | class PooledComponent> extends Component with Pooled {
17 | @override
18 | void _removed() {
19 | moveToPool();
20 | }
21 |
22 | /// If you need to do some cleanup when removing this component override this
23 | /// method.
24 | @override
25 | @visibleForOverriding
26 | void cleanUp() {}
27 | }
28 |
--------------------------------------------------------------------------------
/test/dartemis/core/utils/bag_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('Bag tests', () {
6 | late Bag sut;
7 | setUp(() {
8 | sut = Bag(capacity: 1)
9 | ..add('A')
10 | ..add('B');
11 | });
12 | test('removing an element', () {
13 | sut.remove('A');
14 | expect(sut.contains('A'), equals(false));
15 | expect(sut.contains('B'), equals(true));
16 | expect(sut.size, equals(1));
17 | });
18 | test('removing at position', () {
19 | sut.removeAt(0);
20 | expect(sut.contains('A'), equals(false));
21 | expect(sut.contains('B'), equals(true));
22 | expect(sut.size, equals(1));
23 | });
24 | test('clear', () {
25 | sut.clear();
26 | expect(sut.size, equals(0));
27 | });
28 | test('setting a value by index should not shrink the bag', () {
29 | sut[9] = 'A';
30 | sut[5] = 'B';
31 | expect(sut.size, equals(10));
32 | });
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/lib/dartemis.dart:
--------------------------------------------------------------------------------
1 | library dartemis;
2 |
3 | import 'dart:collection';
4 | import 'dart:core';
5 |
6 | import 'package:meta/meta.dart';
7 |
8 | import 'src/core/utils/bit_set.dart';
9 |
10 | part 'src/core/aspect.dart';
11 | part 'src/core/component.dart';
12 | part 'src/core/component_manager.dart';
13 | part 'src/core/component_type.dart';
14 | part 'src/core/entity.dart';
15 | part 'src/core/entity_manager.dart';
16 | part 'src/core/entity_observer.dart';
17 | part 'src/core/entity_system.dart';
18 | part 'src/core/manager.dart';
19 | part 'src/core/managers/group_manager.dart';
20 | part 'src/core/managers/player_manager.dart';
21 | part 'src/core/managers/tag_manager.dart';
22 | part 'src/core/managers/team_manager.dart';
23 | part 'src/core/mapper.dart';
24 | part 'src/core/systems/entity_processing_system.dart';
25 | part 'src/core/systems/interval_entity_processing_system.dart';
26 | part 'src/core/systems/interval_entity_system.dart';
27 | part 'src/core/systems/void_entity_system.dart';
28 | part 'src/core/utils/bag.dart';
29 | part 'src/core/utils/entity_bag.dart';
30 | part 'src/core/utils/object_pool.dart';
31 | part 'src/core/world.dart';
32 | part 'src/metadata/generate.dart';
33 |
--------------------------------------------------------------------------------
/.github/workflows/dart.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 6 1,15 * *'
7 | push:
8 | branches: [ develop, master ]
9 | pull_request:
10 | branches: [ develop, master ]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v6
18 | - uses: dart-lang/setup-dart@v1.4
19 | with:
20 | sdk: dev
21 |
22 | - name: Install dependencies
23 | run: dart pub get
24 |
25 | - name: Verify formatting
26 | run: dart format --output=none --set-exit-if-changed .
27 |
28 | - name: Analyze
29 | run: dart analyze --fatal-infos
30 |
31 | - name: Run Tests
32 | run: dart test
33 |
34 | - name: Activate test coverage
35 | if: github.event_name != 'pull_request'
36 | run: dart pub global activate -sgit https://github.com/denniskaselow/dart-coveralls.git
37 |
38 | - name: Run test coverage
39 | if: github.event_name != 'pull_request'
40 | run: dart pub global run dart_coveralls report --token ${{ secrets.COVERALLS_TOKEN }} --exclude-test-files test/all_tests.dart --throw-on-error
41 |
--------------------------------------------------------------------------------
/test/dartemis/core/utils/entity_bag_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('EntityBag tests', () {
6 | late EntityBag bag;
7 | final world = World();
8 | final e1 = world.createEntity();
9 | final e2 = world.createEntity();
10 | setUp(() {
11 | bag = EntityBag()
12 | ..add(e1)
13 | ..add(e2);
14 | });
15 | test('removing an element', () {
16 | bag.remove(e1);
17 | expect(bag.contains(e1), equals(false));
18 | expect(bag.contains(e2), equals(true));
19 | expect(bag.length, equals(1));
20 | });
21 | test('iterating', () {
22 | var iter = bag.iterator;
23 | expect(iter.moveNext(), equals(true));
24 | expect(iter.current, equals(e1));
25 | expect(iter.moveNext(), equals(true));
26 | expect(iter.current, equals(e2));
27 |
28 | bag.remove(e1);
29 | iter = bag.iterator;
30 | expect(iter.moveNext(), equals(true));
31 | expect(iter.current, equals(e2));
32 | expect(iter.moveNext(), equals(false));
33 | });
34 | test('clear', () {
35 | bag.clear();
36 | expect(bag.length, equals(0));
37 | });
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/core/utils/entity_bag.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// A [Bag] that uses a [BitSet] to manage entities. Results in faster
4 | /// removal of entities.
5 | class EntityBag with Iterable {
6 | BitSet _entities;
7 |
8 | /// Creates an [EntityBag].
9 | EntityBag() : _entities = BitSet(32);
10 |
11 | /// Add a new [entity]. If the entity already exists, nothing changes.
12 | void add(Entity entity) {
13 | if (entity._id >= _entities.length) {
14 | _entities = BitSet.fromBitSet(_entities, length: entity._id + 1);
15 | }
16 | _entities[entity._id] = true;
17 | }
18 |
19 | /// Removes [entity]. Returns `true` if there was an element and `false`
20 | /// otherwise.
21 | bool remove(Entity entity) {
22 | final result = _entities[entity._id];
23 | _entities[entity._id] = false;
24 | return result;
25 | }
26 |
27 | @override
28 | bool contains(covariant Entity element) => _entities[element._id];
29 |
30 | @override
31 | int get length => _entities.cardinality;
32 |
33 | /// Removes all entites.
34 | void clear() => _entities.clearAll();
35 |
36 | @override
37 | Iterator get iterator =>
38 | _entities.toIntValues().map(Entity._).iterator;
39 | }
40 |
--------------------------------------------------------------------------------
/test/dartemis/core/mapper_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | import 'components_setup.dart';
5 |
6 | void main() {
7 | group('Mapper', () {
8 | late World world;
9 | setUp(() {
10 | world = World();
11 | });
12 | test('gets component for entity', () {
13 | final componentA = Component0();
14 | final componentB = Component1();
15 | final entity = world.createEntity([componentA, componentB]);
16 | world
17 | ..initialize()
18 | ..process();
19 |
20 | final mapper = Mapper(world);
21 |
22 | expect(mapper[entity], equals(componentA));
23 | });
24 | });
25 | group('OptionalMapper', () {
26 | late World world;
27 | setUp(() {
28 | world = World();
29 | });
30 | test('gets component for entity', () {
31 | final componentA = Component0();
32 | final entity = world.createEntity([componentA]);
33 | world
34 | ..initialize()
35 | ..process();
36 |
37 | final mapperA = OptionalMapper(world);
38 | final mapperB = OptionalMapper(world);
39 |
40 | expect(mapperA[entity], equals(componentA));
41 | expect(mapperB[entity], equals(null));
42 | });
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/lib/src/core/systems/interval_entity_system.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// A system that processes entities at a interval in milliseconds.
4 | /// A typical usage would be a collision system or physics system.
5 | abstract class IntervalEntitySystem extends EntitySystem {
6 | double _acc = 0;
7 | double _intervalDelta = 0;
8 |
9 | /// The interval in which the system will be processed.
10 | final double interval;
11 |
12 | /// Create an [IntervalEntitySystem] with the specified [interval] and
13 | /// [aspect].
14 | IntervalEntitySystem(
15 | this.interval,
16 | super.aspect, {
17 | super.group,
18 | super.passive,
19 | });
20 |
21 | /// Returns the accumulated delta since the system was last invoked.
22 | @override
23 | double get delta => _intervalDelta;
24 |
25 | @override
26 | @visibleForOverriding
27 | bool checkProcessing() {
28 | _acc += world.delta;
29 | _intervalDelta += world.delta;
30 | if (_acc >= interval) {
31 | _acc -= interval;
32 | return true;
33 | }
34 | return false;
35 | }
36 |
37 | /// Resets the accumulated delta to 0.
38 | ///
39 | /// Call `super.end()` if you overwrite this function.
40 | @override
41 | @visibleForOverriding
42 | void end() {
43 | _intervalDelta = 0;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example/darteroids/components.dart:
--------------------------------------------------------------------------------
1 | part of '../main.dart';
2 |
3 | class CircularBody extends Component {
4 | num radius;
5 | String color;
6 |
7 | CircularBody(this.radius, this.color);
8 | }
9 |
10 | class Position extends Component {
11 | num _x;
12 | num _y;
13 |
14 | Position(this._x, this._y);
15 |
16 | set x(num x) => _x = x % maxWidth;
17 |
18 | num get x => _x;
19 |
20 | set y(num y) => _y = y % maxHeight;
21 |
22 | num get y => _y;
23 | }
24 |
25 | class Velocity extends Component {
26 | num x;
27 | num y;
28 |
29 | Velocity([this.x = 0, this.y = 0]);
30 | }
31 |
32 | class PlayerDestroyer extends Component {}
33 |
34 | class AsteroidDestroyer extends Component {}
35 |
36 | class Cannon extends Component {
37 | bool shoot = false;
38 | num targetX = 0;
39 | num targetY = 0;
40 | num cooldown = 0;
41 |
42 | void target(num targetX, num targetY) {
43 | this.targetX = targetX;
44 | this.targetY = targetY;
45 | }
46 |
47 | bool get canShoot => shoot && cooldown <= 0;
48 | }
49 |
50 | class Decay extends Component {
51 | num timer;
52 |
53 | Decay(this.timer);
54 | }
55 |
56 | class Status extends Component {
57 | int lifes;
58 | num invisiblityTimer;
59 |
60 | Status({this.lifes = 1, this.invisiblityTimer = 0});
61 |
62 | bool get invisible => invisiblityTimer > 0;
63 | }
64 |
--------------------------------------------------------------------------------
/lib/src/core/managers/team_manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// Use this class together with PlayerManager.
4 | ///
5 | /// You may sometimes want to create teams in your game, so that
6 | /// some players are team mates.
7 | ///
8 | /// A player can only belong to a single team.
9 | class TeamManager extends Manager {
10 | final Map> _playersByTeam;
11 | final Map _teamByPlayer;
12 |
13 | /// Create a TeamManager.
14 | TeamManager()
15 | : _playersByTeam = >{},
16 | _teamByPlayer = {};
17 |
18 | /// Returns the team of [player].
19 | String? getTeam(String player) => _teamByPlayer[player];
20 |
21 | /// Set the [team] of [player].
22 | void setTeam(String player, String team) {
23 | removeFromTeam(player);
24 |
25 | _teamByPlayer[player] = team;
26 | _playersByTeam.putIfAbsent(team, () => []).add(player);
27 | }
28 |
29 | /// Returns all players of [team].
30 | Iterable getPlayers(String team) =>
31 | _playersByTeam[team] ?? [];
32 |
33 | /// Removes [player] from their team.
34 | void removeFromTeam(String player) {
35 | final team = _teamByPlayer.remove(player);
36 | if (team != null) {
37 | _playersByTeam[team]?.remove(player);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright 2011 GAMADU.COM
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/lib/src/core/managers/player_manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// You may sometimes want to specify to which player an entity belongs to.
4 | ///
5 | /// An entity can only belong to a single player at a time.
6 | class PlayerManager extends Manager {
7 | final Map _playerByEntity;
8 | final Map _entitiesByPlayer;
9 |
10 | /// Creates the [PlayerManager].
11 | PlayerManager()
12 | : _playerByEntity = {},
13 | _entitiesByPlayer = {};
14 |
15 | /// Make [entity] belong to [player].
16 | void setPlayer(Entity entity, String player) {
17 | _playerByEntity[entity] = player;
18 | _entitiesByPlayer.putIfAbsent(player, EntityBag.new).add(entity);
19 | }
20 |
21 | /// Returns all entities that belong to [player].
22 | Iterable getEntitiesOfPlayer(String player) =>
23 | _entitiesByPlayer[player] ??= EntityBag();
24 |
25 | /// Removes [entity] from the player it is associated with.
26 | void removeFromPlayer(Entity entity) {
27 | final player = _playerByEntity[entity];
28 | if (player != null) {
29 | _entitiesByPlayer[player]?.remove(entity);
30 | }
31 | }
32 |
33 | /// Returns the player associated with [entity].
34 | String? getPlayer(Entity entity) => _playerByEntity[entity];
35 |
36 | @override
37 | void deleted(Entity entity) => removeFromPlayer(entity);
38 | }
39 |
--------------------------------------------------------------------------------
/test/dartemis/core/systems/interval_entity_system_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('IntervalEntitySystem tests', () {
6 | test('delta returns accumulated time since last processing', () {
7 | final world = World();
8 | final sut = TestIntervalEntitySystem(40);
9 | world
10 | ..addSystem(sut)
11 | ..initialize()
12 | ..delta = 16;
13 |
14 | // ignore: invalid_use_of_visible_for_overriding_member
15 | expect(sut.checkProcessing(), equals(false));
16 | // ignore: invalid_use_of_visible_for_overriding_member
17 | expect(sut.checkProcessing(), equals(false));
18 | // ignore: invalid_use_of_visible_for_overriding_member
19 | expect(sut.checkProcessing(), equals(true));
20 | expect(sut.delta, equals(48));
21 | // ignore: invalid_use_of_visible_for_overriding_member
22 | sut.end();
23 | // ignore: invalid_use_of_visible_for_overriding_member
24 | expect(sut.checkProcessing(), equals(false));
25 | // ignore: invalid_use_of_visible_for_overriding_member
26 | expect(sut.checkProcessing(), equals(true));
27 | expect(sut.delta, equals(32));
28 | });
29 | });
30 | }
31 |
32 | class TestIntervalEntitySystem extends IntervalEntitySystem {
33 | TestIntervalEntitySystem(double interval) : super(interval, Aspect());
34 | @override
35 | void processEntities(Iterable entities) {}
36 | }
37 |
--------------------------------------------------------------------------------
/test/dartemis/core/component_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | import 'components_setup.dart';
5 |
6 | void main() {
7 | group('Component tests', () {
8 | late World world;
9 | setUp(() {
10 | world = World();
11 | });
12 | test('creating a new Component creates a new instance', () {
13 | final entity = world.createEntity();
14 | final c = Component0();
15 | world
16 | ..addComponent(entity, c)
17 | ..removeComponent(entity);
18 |
19 | expect(Component0(), isNot(same(c)));
20 | });
21 | test('creating a new FreeListComponent reuses a removed instance', () {
22 | final entity = world.createEntity();
23 | final c = PooledComponent2();
24 | world.addComponent(entity, c);
25 |
26 | expect(PooledComponent2(), isNot(same(c)));
27 | world.removeComponent(entity);
28 | expect(PooledComponent2(), same(c));
29 | });
30 |
31 | test('moving components should not crash', () {
32 | final entity0 = world.createEntity();
33 | world.addComponent(entity0, PooledComponent2());
34 |
35 | var previousEntity = entity0;
36 | for (var i = 0; i < 128; i++) {
37 | final entity = world.createEntity();
38 | world.moveComponent(previousEntity, entity);
39 | previousEntity = entity;
40 | }
41 |
42 | expect(world.getComponents(previousEntity), isNotEmpty);
43 | });
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/lib/src/core/managers/tag_manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// If you need to tag any entity, use this. A typical usage would be to tag
4 | /// entities such as "PLAYER", "BOSS" or something that is very unique.
5 | /// An entity can only belong to one tag (0,1) at a time.
6 | class TagManager extends Manager {
7 | final Map _entitiesByTag;
8 | final Map _tagsByEntity;
9 |
10 | /// Create the [TagManager].
11 | TagManager()
12 | : _entitiesByTag = {},
13 | _tagsByEntity = {};
14 |
15 | /// Register a [tag] to an [entity].
16 | void register(Entity entity, String tag) {
17 | unregister(tag);
18 | _entitiesByTag[tag] = entity;
19 | _tagsByEntity[entity] = tag;
20 | }
21 |
22 | /// Unregister entity tagged with [tag].
23 | void unregister(String tag) {
24 | _tagsByEntity.remove(_entitiesByTag.remove(tag));
25 | }
26 |
27 | /// Returns [:true:] if there is an entity with [tag].
28 | bool isRegistered(String tag) => _entitiesByTag.containsKey(tag);
29 |
30 | /// Returns the entity with [tag].
31 | Entity? getEntity(String tag) => _entitiesByTag[tag];
32 |
33 | /// Returns the tag of the [entity].
34 | String? getTag(Entity entity) => _tagsByEntity[entity];
35 |
36 | /// Returns all known tags.
37 | Iterable getRegisteredTags() =>
38 | _tagsByEntity.values as Iterable;
39 |
40 | @override
41 | void deleted(Entity entity) {
42 | final removedTag = _tagsByEntity.remove(entity);
43 | if (removedTag != null) {
44 | _entitiesByTag.remove(removedTag);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/test/dartemis/core/managers/group_manager_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('GroupManager tests', () {
6 | late World world;
7 | late GroupManager sut;
8 | late Entity entityA;
9 | late Entity entityAB;
10 | late Entity entity0;
11 | setUp(() {
12 | world = World();
13 | sut = GroupManager();
14 | world.addManager(sut);
15 |
16 | entityA = world.createEntity();
17 | sut.add(entityA, 'A');
18 | entityAB = world.createEntity();
19 | sut
20 | ..add(entityAB, 'A')
21 | ..add(entityAB, 'B');
22 | entity0 = world.createEntity();
23 | });
24 | test('isInAnyGroup', () {
25 | expect(sut.isInAnyGroup(entityA), equals(true));
26 | expect(sut.isInAnyGroup(entityAB), equals(true));
27 | expect(sut.isInAnyGroup(entity0), equals(false));
28 | });
29 | test('isInGroup', () {
30 | expect(sut.isInGroup(entityA, 'A'), equals(true));
31 | expect(sut.isInGroup(entityAB, 'A'), equals(true));
32 | expect(sut.isInGroup(entity0, 'A'), equals(false));
33 | expect(sut.isInGroup(entityA, 'B'), equals(false));
34 | expect(sut.isInGroup(entityAB, 'B'), equals(true));
35 | expect(sut.isInGroup(entity0, 'B'), equals(false));
36 | });
37 | test('isInGroup after add and remove', () {
38 | final entity00 = world.createEntity();
39 | expect(sut.isInGroup(entity00, 'A'), equals(false));
40 | sut.add(entity00, 'A');
41 | expect(sut.isInGroup(entity00, 'A'), equals(true));
42 | sut.remove(entity00, 'A');
43 | expect(sut.isInGroup(entity00, 'A'), equals(false));
44 | });
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/lib/src/metadata/generate.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// Metadata to annotate [Manager]s and [EntitySystem]s to generate code
4 | /// required for [Mapper]s, other [Manager]s and other [EntitySystem]s using
5 | /// dartemis_builder.
6 | class Generate {
7 | /// The [EntitySystem] or [Manager] that is the base class.
8 | final Type base;
9 |
10 | /// Additional mappers to declare and initialize.
11 | final List mapper;
12 |
13 | /// Other [EntitySystem]s to declare and initialize.
14 | final List systems;
15 |
16 | /// Other [Manager]s to declare and initialize.
17 | final List manager;
18 |
19 | /// All [Aspect]s that an [int] needs to be processed by the
20 | /// [EntitySystem].
21 | /// The required [Mapper]s will also be created.
22 | ///
23 | /// Has no effect if used in a [Manager].
24 | final List allOf;
25 |
26 | /// One of the [Aspect]s that an [int] needs to be processed by the
27 | /// [EntitySystem]. Required [Mapper]s will also be created.
28 | ///
29 | /// Has no effect if used in a [Manager].
30 | final List oneOf;
31 |
32 | /// Excludes [int]s that have these [Aspect]s from being processed by the
33 | /// [EntitySystem].
34 | ///
35 | /// Has no effect if used in a [Manager].
36 | final List exclude;
37 |
38 | /// Generate a class that extends [base] with an [Aspect] based on [allOf],
39 | /// [oneOf] and [exclude] as well as the additional [Mapper]s defined by
40 | /// [mapper] and the [EntitySystem]s and [Manager]s defined by [systems] and
41 | /// [manager].
42 | const Generate(
43 | this.base, {
44 | this.allOf = const [],
45 | this.oneOf = const [],
46 | this.exclude = const [],
47 | this.mapper = const [],
48 | this.systems = const [],
49 | this.manager = const [],
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/test/dartemis/core/components_setup.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 |
3 | class Component0 extends Component {}
4 |
5 | class Component1 extends Component {}
6 |
7 | class PooledComponent2 extends PooledComponent {
8 | factory PooledComponent2() => Pooled.of(PooledComponent2._);
9 | PooledComponent2._();
10 | }
11 |
12 | class Component3 extends Component {}
13 |
14 | class Component4 extends Component {}
15 |
16 | class Component5 extends Component {}
17 |
18 | class Component6 extends Component {}
19 |
20 | class Component7 extends Component {}
21 |
22 | class Component8 extends Component {}
23 |
24 | class Component9 extends Component {}
25 |
26 | class Component10 extends Component {}
27 |
28 | class Component11 extends Component {}
29 |
30 | class Component12 extends Component {}
31 |
32 | class Component13 extends Component {}
33 |
34 | class Component14 extends Component {}
35 |
36 | class Component15 extends Component {}
37 |
38 | class Component16 extends Component {}
39 |
40 | class Component17 extends Component {}
41 |
42 | class Component18 extends Component {}
43 |
44 | class Component19 extends Component {}
45 |
46 | class Component20 extends Component {}
47 |
48 | class Component21 extends Component {}
49 |
50 | class Component22 extends Component {}
51 |
52 | class Component23 extends Component {}
53 |
54 | class Component24 extends Component {}
55 |
56 | class Component25 extends Component {}
57 |
58 | class Component26 extends Component {}
59 |
60 | class Component27 extends Component {}
61 |
62 | class Component28 extends Component {}
63 |
64 | class Component29 extends Component {}
65 |
66 | class Component30 extends Component {}
67 |
68 | class Component31 extends Component {}
69 |
70 | class Component32 extends Component {}
71 |
72 | class UnusedComponent extends Component {}
73 |
--------------------------------------------------------------------------------
/lib/src/core/mapper.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// High performance component retrieval from entities. Use this wherever you
4 | /// need to retrieve components from entities often and fast.
5 | class Mapper {
6 | final List _components;
7 |
8 | /// Create a Mapper for [T] in [world].
9 | Mapper(World world)
10 | : _components = world.componentManager._getComponentsByType();
11 |
12 | /// Fast but unsafe retrieval of a component for this entity.
13 | /// No bounding checks, so this could throw a [RangeError],
14 | /// however in most scenarios you already know the entity possesses this
15 | /// component.
16 | T operator [](Entity entity) => _components[entity._id]!;
17 |
18 | /// Fast and safe retrieval of a component for this entity.
19 | /// If the entity does not have this component then null is returned.
20 | T? getSafe(Entity entity) {
21 | if (_components.length > entity._id) {
22 | return _components[entity._id];
23 | }
24 | return null;
25 | }
26 |
27 | /// Checks if the entity has this type of component.
28 | bool has(Entity entity) => getSafe(entity) != null;
29 | }
30 |
31 | /// Same as [Mapper], except the [[]] operator returns [T?] instead of [T] and
32 | /// no getSafe method.
33 | /// For use in combination with [Aspect.oneOf].
34 | class OptionalMapper {
35 | final List _components;
36 |
37 | /// Create a Mapper for [T] in [world].
38 | OptionalMapper(World world)
39 | : _components = world.componentManager._getComponentsByType();
40 |
41 | /// Fast and safe retrieval of a component for this entity.
42 | /// If the entity does not have this component then null is returned.
43 | T? operator [](Entity entity) {
44 | if (_components.length > entity._id) {
45 | return _components[entity._id];
46 | }
47 | return null;
48 | }
49 |
50 | /// Checks if the entity has this type of component.
51 | bool has(Entity entity) => this[entity] != null;
52 | }
53 |
--------------------------------------------------------------------------------
/lib/src/core/managers/group_manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// If you need to group your entities together, e.g. tanks going into "units"
4 | /// group or explosions into "effects", then use this manager. You must retrieve
5 | /// it using world instance.
6 | ///
7 | /// An [int] can only belong to several groups (0,n) at a time.
8 | class GroupManager extends Manager {
9 | final Map _entitiesByGroup;
10 | final Map> _groupsByEntity;
11 |
12 | /// Creates the [GroupManager].
13 | GroupManager()
14 | : _entitiesByGroup = {},
15 | _groupsByEntity = >{};
16 |
17 | /// Set the group of the entity.
18 | void add(Entity entity, String group) {
19 | _entitiesByGroup.putIfAbsent(group, EntityBag.new).add(entity);
20 | _groupsByEntity.putIfAbsent(entity, Bag.new).add(group);
21 | }
22 |
23 | /// Remove the entity from the specified group.
24 | void remove(Entity entity, String group) {
25 | _entitiesByGroup[group]?.remove(entity);
26 | _groupsByEntity[entity]?.remove(group);
27 | }
28 |
29 | /// Remove [entity] from all existing groups.
30 | void removeFromAllGroups(Entity entity) {
31 | final groups = _groupsByEntity[entity];
32 | if (groups != null) {
33 | groups
34 | ..forEach((group) {
35 | _entitiesByGroup[group]?.remove(entity);
36 | })
37 | ..clear();
38 | }
39 | }
40 |
41 | /// Get all entities that belong to the provided group.
42 | Iterable getEntities(String group) =>
43 | _entitiesByGroup.putIfAbsent(group, EntityBag.new);
44 |
45 | /// Returns the groups the entity belongs to, null if none.
46 | Iterable? getGroups(Entity entity) => _groupsByEntity[entity];
47 |
48 | /// Checks if the entity belongs to any group.
49 | bool isInAnyGroup(Entity entity) => getGroups(entity) != null;
50 |
51 | /// Check if the entity is in the supplied group.
52 | bool isInGroup(Entity entity, String group) {
53 | final groups = _groupsByEntity[entity];
54 | return (groups != null) && groups.contains(group);
55 | }
56 |
57 | @override
58 | void deleted(Entity entity) => removeFromAllGroups(entity);
59 | }
60 |
--------------------------------------------------------------------------------
/lib/src/core/entity_manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// Manages creation and deletion of every [int] and gives access to some
4 | /// basic statistcs.
5 | class EntityManager extends Manager {
6 | BitSet _entities;
7 | final Bag _deletedEntities;
8 |
9 | int _active = 0;
10 | int _added = 0;
11 | int _created = 0;
12 | int _deleted = 0;
13 |
14 | final _EntityPool _identifierPool;
15 |
16 | EntityManager._internal()
17 | : _entities = BitSet(32),
18 | _deletedEntities = Bag(),
19 | _identifierPool = _EntityPool();
20 |
21 | Entity _createEntityInstance() {
22 | final entity = _deletedEntities.removeLast() ?? _identifierPool.checkOut();
23 | _created++;
24 | return entity;
25 | }
26 |
27 | void _add(Entity entity) {
28 | _active++;
29 | _added++;
30 | if (entity._id >= _entities.length) {
31 | _entities = BitSet.fromBitSet(_entities, length: entity._id + 1);
32 | }
33 | _entities[entity._id] = true;
34 | }
35 |
36 | void _delete(Entity entity) {
37 | if (_entities[entity._id]) {
38 | _entities[entity._id] = false;
39 |
40 | _deletedEntities.add(entity);
41 |
42 | _active--;
43 | _deleted++;
44 | }
45 | }
46 |
47 | /// Check if this entity is active.
48 | /// Active means the entity is being actively processed.
49 | bool isActive(Entity entity) => _entities[entity._id];
50 |
51 | /// Get how many entities are active in this world.
52 | int get activeEntityCount => _active;
53 |
54 | /// Get how many entities have been created in the world since start.
55 | /// Note: A created entity may not have been added to the world, thus
56 | /// created count is always equal or larger than added count.
57 | int get totalCreated => _created;
58 |
59 | /// Get how many entities have been added to the world since start.
60 | int get totalAdded => _added;
61 |
62 | /// Get how many entities have been deleted from the world since start.
63 | int get totalDeleted => _deleted;
64 | }
65 |
66 | /// Used only internally to generate distinct ids for entities and reuse them.
67 | class _EntityPool {
68 | final List _entities = [];
69 | int _nextAvailableId = 0;
70 |
71 | _EntityPool();
72 |
73 | Entity checkOut() {
74 | if (_entities.isNotEmpty) {
75 | return _entities.removeLast();
76 | }
77 | return Entity._(_nextAvailableId++);
78 | }
79 |
80 | void checkIn(Entity entity) => _entities.add(entity);
81 | }
82 |
--------------------------------------------------------------------------------
/lib/src/core/utils/object_pool.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// Inspired by
4 | /// this class stores objects that are no longer used in the game for later
5 | /// reuse.
6 | class ObjectPool {
7 | static final Map> _objectPools = >{};
8 |
9 | /// Returns a pooled object of type [T]. If there is no object in the pool
10 | /// it will create a new one using [createPooled].
11 | static T get>(CreatePooled createPooled) {
12 | final pool = _getPool();
13 | var obj = pool.removeLast();
14 | return obj ??= createPooled();
15 | }
16 |
17 | static Bag _getPool>() {
18 | var pooledObjects = _objectPools[T] as Bag?;
19 | if (null == pooledObjects) {
20 | pooledObjects = Bag();
21 | _objectPools[T] = pooledObjects;
22 | }
23 | return pooledObjects;
24 | }
25 |
26 | /// Adds a [Pooled] object to the [ObjectPool].
27 | static void add>(T pooled) {
28 | _getPool().add(pooled);
29 | }
30 |
31 | /// Add a specific [amount] of [Pooled]s for later reuse.
32 | static void addMany>(
33 | CreatePooled createPooled,
34 | int amount,
35 | ) {
36 | final pool = _getPool();
37 | for (var i = 0; i < amount; i++) {
38 | pool.add(createPooled());
39 | }
40 | }
41 | }
42 |
43 | /// Create a [Pooled] object.
44 | typedef CreatePooled> = T Function();
45 |
46 | /// Objects of this class can be pooled in the [ObjectPool] for later reuse.
47 | ///
48 | /// Should be added as a mixin.
49 | mixin Pooled> {
50 | /// Creates a new [Pooled] of type [T].
51 | ///
52 | /// The instance created with [createPooled] should be created with
53 | /// a zero-argument contructor because it will only be called once. All fields
54 | /// of the created object should be set in the calling factory constructor.
55 | static T of>(CreatePooled createPooled) =>
56 | ObjectPool.get(createPooled);
57 |
58 | /// If you need to do some cleanup before this object moves into the Pool of
59 | /// reusable objects.
60 | void cleanUp();
61 |
62 | /// Calls the cleanup function and moves this object to the [ObjectPool].
63 | void moveToPool() {
64 | cleanUp();
65 | ObjectPool.add(this as T);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/test/dartemis/core/managers/tag_manager_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | group('TagManager tests', () {
6 | const tag = 'some tag';
7 |
8 | late World world;
9 | late TagManager sut;
10 | late Entity entityWithTag;
11 | late Entity entityWithoutTag;
12 | setUp(() {
13 | world = World();
14 | sut = TagManager();
15 | world.addManager(sut);
16 |
17 | entityWithTag = world.createEntity();
18 | entityWithoutTag = world.createEntity();
19 |
20 | sut.register(entityWithTag, tag);
21 | });
22 | test('getEntity returns registered entity', () {
23 | final actualEntity = sut.getEntity(tag);
24 |
25 | expect(actualEntity, equals(entityWithTag));
26 | });
27 | test('getEntity returns null if tag has been unregistered', () {
28 | sut.unregister(tag);
29 |
30 | final actualEntity = sut.getEntity(tag);
31 |
32 | expect(actualEntity, isNull);
33 | });
34 | test('getEntity returns null if tag does not exist', () {
35 | final actualEntity = sut.getEntity('nonexistent tag');
36 |
37 | expect(actualEntity, isNull);
38 | });
39 | test('getTag returns registered tag', () {
40 | final actualTag = sut.getTag(entityWithTag);
41 |
42 | expect(actualTag, equals(tag));
43 | });
44 | test('getTag returns null if entity has no tag', () {
45 | final actualTag = sut.getTag(entityWithoutTag);
46 |
47 | expect(actualTag, isNull);
48 | });
49 | test('getTag returns null if tag has been unregistered', () {
50 | sut.unregister(tag);
51 |
52 | final actualTag = sut.getTag(entityWithTag);
53 |
54 | expect(actualTag, isNull);
55 | });
56 | test(
57 | '''register overwrites existing entity if a another entity is registered using the same tag''',
58 | () {
59 | final anotherEntity = world.createEntity();
60 | sut.register(anotherEntity, 'tag');
61 |
62 | final actualEntity = sut.getEntity('tag');
63 | expect(actualEntity, equals(anotherEntity));
64 | });
65 | test(
66 | '''deleting a previously registered entity does not mess up accessing a newly registered entity''',
67 | () {
68 | final anotherEntity = world.createEntity();
69 | sut.register(anotherEntity, 'tag');
70 | world.deleteEntity(entityWithTag);
71 |
72 | final actualEntity = sut.getEntity('tag');
73 | expect(actualEntity, equals(anotherEntity));
74 | });
75 | });
76 | }
77 |
--------------------------------------------------------------------------------
/test/dartemis/core/aspect_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | import 'components_setup.dart';
5 |
6 | void main() {
7 | group('Aspect Tests', () {
8 | test('getAspectForAll with one component', () {
9 | final aspect = Aspect(allOf: [PooledComponent2]);
10 | expect(aspect.all, contains(PooledComponent2));
11 | expect(aspect.all, hasLength(1));
12 | expect(aspect.excluded, isEmpty);
13 | expect(aspect.one, isEmpty);
14 | });
15 | test('getAspectForAll with all components', () {
16 | final aspect = Aspect(allOf: [Component0, Component1, PooledComponent2]);
17 | expect(
18 | aspect.all,
19 | containsAll([Component0, Component1, PooledComponent2]),
20 | );
21 | expect(aspect.excluded, isEmpty);
22 | expect(aspect.one, isEmpty);
23 | });
24 | test('getAspectForAll with one component, excluding another one', () {
25 | final aspect = Aspect(allOf: [PooledComponent2])..exclude([Component0]);
26 | expect(aspect.all, containsAll([PooledComponent2]));
27 | expect(aspect.excluded, containsAll([Component0]));
28 | expect(aspect.one, isEmpty);
29 | });
30 | test('getAspectForAll with one component, excluding another two', () {
31 | final aspect = Aspect(allOf: [PooledComponent2])
32 | ..exclude([Component0, Component1]);
33 | expect(aspect.all, containsAll([PooledComponent2]));
34 | expect(aspect.excluded, containsAll([Component0, Component1]));
35 | expect(aspect.one, isEmpty);
36 | });
37 | test('getAspectForAll with one component, and one of two', () {
38 | final aspect = Aspect(allOf: [PooledComponent2])
39 | ..oneOf([Component0, Component1]);
40 | expect(aspect.all, containsAll([PooledComponent2]));
41 | expect(aspect.excluded, isEmpty);
42 | expect(aspect.one, containsAll([Component0, Component1]));
43 | });
44 | test('getAspectForOne with all components', () {
45 | final aspect = Aspect(oneOf: [Component0, Component1, PooledComponent2]);
46 | expect(aspect.all, isEmpty);
47 | expect(aspect.excluded, isEmpty);
48 | expect(
49 | aspect.one,
50 | containsAll([Component0, Component1, PooledComponent2]),
51 | );
52 | });
53 | test('getAspectForOne with chaining each component', () {
54 | final aspect = Aspect(oneOf: [Component0])
55 | ..oneOf([Component1])
56 | ..oneOf([PooledComponent2]);
57 | expect(aspect.all, isEmpty);
58 | expect(aspect.excluded, isEmpty);
59 | expect(
60 | aspect.one,
61 | containsAll([Component0, Component1, PooledComponent2]),
62 | );
63 | });
64 | test('getEmpty()', () {
65 | final aspect = Aspect();
66 | expect(aspect.all, isEmpty);
67 | expect(aspect.excluded, isEmpty);
68 | expect(aspect.one, isEmpty);
69 | });
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/example/darteroids/input_systems.dart:
--------------------------------------------------------------------------------
1 | part of '../main.dart';
2 |
3 | class PlayerControlSystem extends IntervalEntitySystem {
4 | static const int up = KeyCode.W;
5 | static const int down = KeyCode.S;
6 | static const int left = KeyCode.A;
7 | static const int right = KeyCode.D;
8 |
9 | bool moveUp = false;
10 | bool moveDown = false;
11 | bool moveLeft = false;
12 | bool moveRight = false;
13 | bool shoot = false;
14 |
15 | num targetX = 0;
16 | num targetY = 0;
17 |
18 | late final Mapper velocityMapper;
19 | late final Mapper cannonMapper;
20 | late final TagManager tagManager;
21 |
22 | final HTMLCanvasElement canvas;
23 |
24 | PlayerControlSystem(this.canvas)
25 | : super(20, Aspect(allOf: [Velocity, Cannon]));
26 |
27 | @override
28 | void initialize(World world) {
29 | super.initialize(world);
30 | tagManager = world.getManager();
31 | velocityMapper = Mapper(world);
32 | cannonMapper = Mapper(world);
33 |
34 | window.onKeyDown.listen(handleKeyDown);
35 | EventStreamProviders.keyUpEvent.forTarget(window).listen(handleKeyUp);
36 | canvas.onMouseDown.listen(handleMouseDown);
37 | canvas.onMouseUp.listen(handleMouseUp);
38 | }
39 |
40 | @override
41 | void processEntities(Iterable entities) {
42 | final player = tagManager.getEntity(tagPlayer)!;
43 | final velocity = velocityMapper[player];
44 | final cannon = cannonMapper[player];
45 |
46 | if (moveUp) {
47 | velocity.y -= 0.1;
48 | } else if (moveDown) {
49 | velocity.y += 0.1;
50 | }
51 | if (moveLeft) {
52 | velocity.x -= 0.1;
53 | } else if (moveRight) {
54 | velocity.x += 0.1;
55 | }
56 | cannon.shoot = shoot;
57 | if (shoot) {
58 | cannon.target(targetX, targetY);
59 | }
60 | }
61 |
62 | void handleKeyDown(KeyboardEvent e) {
63 | final keyCode = e.keyCode;
64 | if (keyCode == up) {
65 | moveUp = true;
66 | moveDown = false;
67 | } else if (keyCode == down) {
68 | moveUp = false;
69 | moveDown = true;
70 | } else if (keyCode == left) {
71 | moveLeft = true;
72 | moveRight = false;
73 | } else if (keyCode == right) {
74 | moveLeft = false;
75 | moveRight = true;
76 | }
77 | }
78 |
79 | void handleKeyUp(KeyboardEvent e) {
80 | final keyCode = e.keyCode;
81 | if (keyCode == up) {
82 | moveUp = false;
83 | } else if (keyCode == down) {
84 | moveDown = false;
85 | } else if (keyCode == left) {
86 | moveLeft = false;
87 | } else if (keyCode == right) {
88 | moveRight = false;
89 | }
90 | }
91 |
92 | void handleMouseDown(MouseEvent e) {
93 | targetX = e.offsetX;
94 | targetY = e.offsetY;
95 | shoot = true;
96 | }
97 |
98 | void handleMouseUp(MouseEvent e) {
99 | shoot = false;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/src/core/aspect.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// An Aspect is used by systems as a matcher against entities, to check if a
4 | /// system is interested in an entity. Aspects define what sort of component
5 | /// types an entity must possess, or not possess.
6 | ///
7 | /// This creates an aspect where an entity must possess A and B and C:
8 | /// Aspect(allOf: [A, B, C])
9 | ///
10 | /// This creates an aspect where an entity must possess A and B and C, but must
11 | /// not possess U or V.
12 | /// Aspect(allOf: [A, B, C])..exclude([U, V])
13 | ///
14 | /// This creates an aspect where an entity must possess A and B and C, but must
15 | /// not possess U or V, but must possess one of X or Y or Z.
16 | /// Aspect(allOf: [A, B, C])..exclude([U, V])..oneOf([X, Y, Z])
17 | ///
18 | /// You can create and compose aspects in many ways:
19 | /// Aspect.empty()..oneOf([X, Y, Z])..allOf([A, B, C])..exclude([U, V])
20 | /// is the same as:
21 | /// Aspect(allOf: [A, B, C])..exclude([U, V])..oneOf([X, Y, Z])
22 | class Aspect {
23 | /// All components an [Entity] needs to be processed by an [EntitySystem].
24 | final Set all = {};
25 |
26 | /// An [Entity] needs one of these components to be processed by the
27 | /// [EntitySystem].
28 | final Set one = {};
29 |
30 | /// An [Entity] will not be processed by the [EntitySystem] if it has one of
31 | /// these [Component] types.
32 | final Set excluded = {};
33 |
34 | /// Creates and returns an aspect.
35 | ///
36 | /// A system only processes an [Entity] that posses all [Component]s
37 | /// given by [allOf].
38 | ///
39 | /// With [oneOf] an [Entity] must posses at least one of the specified
40 | /// [Component]s to be processed by a system.
41 | ///
42 | /// [exclude] can be used to prevent an [Entity] to be processed when it has
43 | /// one of the specified [Component]s.
44 | ///
45 | /// If no arguments are passed it will be an empty aspect.
46 | /// This can be used if you want a system that processes no entities,
47 | /// but still gets invoked. Typical usages is when
48 | /// you need to create special purpose systems for debug rendering, like
49 | /// rendering FPS, how many entities are active in the world, etc.
50 | Aspect({
51 | Iterable allOf = const {},
52 | Iterable oneOf = const {},
53 | Iterable exclude = const {},
54 | }) {
55 | all.addAll(allOf);
56 | one.addAll(oneOf);
57 | excluded.addAll(exclude);
58 | }
59 |
60 | /// Modifies the aspect in a way that an entity must possess all of the
61 | /// specified components.
62 | void allOf(Iterable componentTypes) {
63 | all.addAll(componentTypes);
64 | }
65 |
66 | /// Excludes all of the specified components from the aspect. A system will
67 | /// not be interested in an entity that possesses one of the specified
68 | /// excluded components.
69 | void exclude(Iterable componentTypes) {
70 | excluded.addAll(componentTypes);
71 | }
72 |
73 | /// Modifies the aspect in a way that an entity must possess one of the
74 | /// specified components.
75 | void oneOf(Iterable componentTypes) {
76 | one.addAll(componentTypes);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/example/main.dart:
--------------------------------------------------------------------------------
1 | library darteroids;
2 |
3 | import 'dart:async';
4 | import 'dart:js_interop';
5 | import 'dart:math';
6 |
7 | import 'package:dartemis/dartemis.dart';
8 | import 'package:web/web.dart';
9 |
10 | part 'darteroids/components.dart';
11 | part 'darteroids/gamelogic_systems.dart';
12 | part 'darteroids/input_systems.dart';
13 | part 'darteroids/render_systems.dart';
14 |
15 | const String tagPlayer = 'player';
16 | const String groupAsteroids = 'ASTEROIDS';
17 | const String playerColor = '#ff0000';
18 | const String asteroidColor = '#BBB';
19 | const int maxWidth = 600;
20 | const int maxHeight = 600;
21 | const int hudHeight = 100;
22 |
23 | final Random random = Random();
24 |
25 | void main() async {
26 | final canvas = document.querySelector('#gamecontainer')! as HTMLCanvasElement
27 | ..width = maxWidth
28 | ..height = maxHeight + hudHeight;
29 |
30 | await Darteroids(canvas).start();
31 | }
32 |
33 | class Darteroids {
34 | final HTMLCanvasElement canvas;
35 | final CanvasRenderingContext2D context2d;
36 | final World world;
37 | num lastTime = 0;
38 | final Stopwatch physicsLoopTimer = Stopwatch()..start();
39 |
40 | Darteroids(this.canvas)
41 | : context2d = canvas.context2D,
42 | world = World();
43 |
44 | Future start() async {
45 | final player = world.createEntity([
46 | Position(maxWidth ~/ 2, maxHeight ~/ 2),
47 | Velocity(),
48 | CircularBody(20, playerColor),
49 | Cannon(),
50 | Status(lifes: 3, invisiblityTimer: 5000),
51 | ]);
52 |
53 | final tagManager = TagManager()..register(player, tagPlayer);
54 | final groupManager = GroupManager();
55 | world
56 | ..addManager(tagManager)
57 | ..addManager(groupManager);
58 |
59 | addAsteroids(groupManager);
60 |
61 | world
62 | ..addSystem(PlayerControlSystem(canvas))
63 | ..addSystem(BulletSpawningSystem())
64 | ..addSystem(DecaySystem())
65 | ..addSystem(MovementSystem())
66 | ..addSystem(AsteroidDestructionSystem())
67 | ..addSystem(PlayerCollisionDetectionSystem())
68 | ..addSystem(BackgroundRenderSystem(context2d, group: 1))
69 | ..addSystem(CircleRenderingSystem(context2d, group: 1))
70 | ..addSystem(HudRenderSystem(context2d, group: 1))
71 | ..initialize();
72 |
73 | physicsLoop();
74 | renderLoop(16.66);
75 | }
76 |
77 | void addAsteroids(GroupManager groupManager) {
78 | for (var i = 0; i < 33; i++) {
79 | final vx = generateRandomVelocity();
80 | final vy = generateRandomVelocity();
81 | final asteroid = world.createEntity([
82 | Position(
83 | maxWidth * random.nextDouble(),
84 | maxHeight * random.nextDouble(),
85 | ),
86 | Velocity(vx, vy),
87 | CircularBody(5 + 10 * random.nextDouble(), asteroidColor),
88 | PlayerDestroyer(),
89 | ]);
90 | groupManager.add(asteroid, groupAsteroids);
91 | }
92 | }
93 |
94 | void physicsLoop() {
95 | world
96 | ..delta = physicsLoopTimer.elapsedMicroseconds / 1000.0
97 | ..process();
98 | physicsLoopTimer.reset();
99 |
100 | Future.delayed(const Duration(milliseconds: 5), physicsLoop);
101 | }
102 |
103 | void renderLoop(num time) {
104 | world.delta = (time - lastTime).toDouble();
105 | lastTime = time;
106 | world.process(1);
107 |
108 | window.requestAnimationFrame(renderLoop.toJS);
109 | }
110 | }
111 |
112 | num generateRandomVelocity() =>
113 | 0.5 + 1.5 * random.nextDouble() * (random.nextBool() ? 1 : -1);
114 |
115 | bool doCirclesCollide(
116 | num x1,
117 | num y1,
118 | num radius1,
119 | num x2,
120 | num y2,
121 | num radius2,
122 | ) {
123 | final dx = x2 - x1;
124 | final dy = y2 - y1;
125 | final d = radius1 + radius2;
126 | return (dx * dx + dy * dy) < (d * d);
127 | }
128 |
--------------------------------------------------------------------------------
/example/darteroids/render_systems.dart:
--------------------------------------------------------------------------------
1 | part of '../main.dart';
2 |
3 | class CircleRenderingSystem extends EntityProcessingSystem {
4 | final CanvasRenderingContext2D context;
5 |
6 | late final Mapper positionMapper;
7 | late final Mapper bodyMapper;
8 | late final Mapper statusMapper;
9 |
10 | CircleRenderingSystem(this.context, {super.group})
11 | : super(Aspect(allOf: [Position, CircularBody]));
12 |
13 | @override
14 | void initialize(World world) {
15 | super.initialize(world);
16 | positionMapper = Mapper(world);
17 | statusMapper = Mapper(world);
18 | bodyMapper = Mapper(world);
19 | }
20 |
21 | @override
22 | void processEntity(Entity entity) {
23 | final pos = positionMapper[entity];
24 | final body = bodyMapper[entity];
25 | final status = statusMapper.getSafe(entity);
26 |
27 | context.save();
28 |
29 | try {
30 | context
31 | ..lineWidth = 0.5
32 | ..fillStyle = body.color.toJS
33 | ..strokeStyle = body.color.toJS;
34 | if (null != status && status.invisible) {
35 | if (status.invisiblityTimer % 600 < 300) {
36 | context.globalAlpha = 0.4;
37 | }
38 | }
39 |
40 | drawCirle(pos, body);
41 |
42 | if (pos.x + body.radius > maxWidth) {
43 | drawCirle(pos, body, offsetX: -maxWidth);
44 | } else if (pos.x - body.radius < 0) {
45 | drawCirle(pos, body, offsetX: maxWidth);
46 | }
47 | if (pos.y + body.radius > maxHeight) {
48 | drawCirle(pos, body, offsetY: -maxHeight);
49 | } else if (pos.y - body.radius < 0) {
50 | drawCirle(pos, body, offsetY: maxHeight);
51 | }
52 |
53 | context.stroke();
54 | } finally {
55 | context.restore();
56 | }
57 | }
58 |
59 | void drawCirle(
60 | Position pos,
61 | CircularBody body, {
62 | int offsetX = 0,
63 | int offsetY = 0,
64 | }) {
65 | context
66 | ..beginPath()
67 | ..arc(pos.x + offsetX, pos.y + offsetY, body.radius, 0, pi * 2)
68 | ..closePath()
69 | ..fill();
70 | }
71 | }
72 |
73 | class BackgroundRenderSystem extends VoidEntitySystem {
74 | final CanvasRenderingContext2D context;
75 |
76 | BackgroundRenderSystem(this.context, {super.group});
77 |
78 | @override
79 | void processSystem() {
80 | context.save();
81 | try {
82 | context
83 | ..fillStyle = 'black'.toJS
84 | ..beginPath()
85 | ..rect(0, 0, maxWidth, maxHeight + hudHeight)
86 | ..closePath()
87 | ..fill();
88 | } finally {
89 | context.restore();
90 | }
91 | }
92 | }
93 |
94 | class HudRenderSystem extends VoidEntitySystem {
95 | final CanvasRenderingContext2D context;
96 | late final TagManager tagManager;
97 | late final Mapper statusMapper;
98 |
99 | HudRenderSystem(this.context, {super.group});
100 |
101 | @override
102 | void initialize(World world) {
103 | super.initialize(world);
104 | tagManager = world.getManager();
105 | statusMapper = Mapper(world);
106 | }
107 |
108 | @override
109 | void processSystem() {
110 | context.save();
111 | try {
112 | context
113 | ..fillStyle = '#555'.toJS
114 | ..beginPath()
115 | ..rect(0, maxHeight, maxWidth, maxHeight + hudHeight)
116 | ..closePath()
117 | ..fill();
118 |
119 | final player = tagManager.getEntity(tagPlayer)!;
120 | final status = statusMapper[player];
121 |
122 | context.fillStyle = playerColor.toJS;
123 | for (var i = 0; i < status.lifes; i++) {
124 | context
125 | ..beginPath()
126 | ..arc(50 + i * 50, maxHeight + hudHeight ~/ 2, 15, 0, pi * 2)
127 | ..closePath()
128 | ..fill();
129 | }
130 | } finally {
131 | context.restore();
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/lib/src/core/utils/bag.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dartemis.dart';
2 |
3 | /// Collection type a bit like List but does not preserve the order of its
4 | /// entities, speedwise it is very good, especially suited for games.
5 | class Bag with Iterable {
6 | List _data;
7 | int _size = 0;
8 |
9 | /// Create a [Bag] with an initial capacity of [capacity].
10 | Bag({int capacity = 32}) : _data = List.filled(capacity, null);
11 |
12 | /// Creates a new [Bag] with the elements of [iterable].
13 | Bag.from(Iterable iterable)
14 | : _data = iterable.toList(growable: false),
15 | _size = iterable.length;
16 |
17 | /// Returns the element at the specified [index] in the bag.
18 | E? operator [](int index) => _data[index];
19 |
20 | /// Returns the number of elements in this bag.
21 | int get size => _size;
22 |
23 | /// Returns [:true:] if this bag contains no elements.
24 | @override
25 | bool get isEmpty => size == 0;
26 |
27 | /// Removes the element at the specified [index] in this bag. Does this by
28 | /// overwriting with the last element and then removing the last element.
29 | E? removeAt(int index) {
30 | // make copy of element to remove so it can be returned
31 | final o = _data[index];
32 | // overwrite item to remove with last element
33 | _data[index] = _data[--_size];
34 | // null last element, so gc can do its work
35 | _data[size] = null;
36 |
37 | return o;
38 | }
39 |
40 | /// Remove and return the last object in the bag.
41 | E? removeLast() {
42 | if (_size > 0) {
43 | final current = _data[--_size];
44 | _data[size] = null;
45 | return current;
46 | }
47 | return null;
48 | }
49 |
50 | /// Removes the first occurrence of the specified element from this bag, if
51 | /// it is present. If the Bag does not contain the element, it is unchanged.
52 | /// Does this by overwriting with the last element and then removing the last
53 | /// element.
54 | /// Returns [:true:] if this list contained the specified [element].
55 | bool remove(E element) {
56 | for (var i = 0; i < size; i++) {
57 | final current = _data[i];
58 |
59 | if (element == current) {
60 | // overwrite item to remove with last element
61 | _data[i] = _data[--_size];
62 | // null last element, so gc can do its work
63 | _data[size] = null;
64 | return true;
65 | }
66 | }
67 |
68 | return false;
69 | }
70 |
71 | /// Returns the number of elements the bag can hold without growing.
72 | int get capacity => _data.length;
73 |
74 | /// Adds the specified [element] to the end of this bag. If needed also
75 | /// increases the capacity of the bag.
76 | void add(E element) {
77 | // is size greater than capacity increase capacity
78 | if (_size == _data.length) {
79 | _grow();
80 | }
81 | _data[_size++] = element;
82 | }
83 |
84 | /// Sets [element] at specified [index] in the bag.
85 | void operator []=(int index, E element) {
86 | if (index >= _data.length) {
87 | _growTo(index * 2);
88 | }
89 | if (_size <= index) {
90 | _size = index + 1;
91 | }
92 | _data[index] = element;
93 | }
94 |
95 | void _grow() => _growTo(_calculateNewCapacity(_data.length));
96 |
97 | int _calculateNewCapacity(int requiredLength) =>
98 | (requiredLength * 3) ~/ 2 + 1;
99 |
100 | void _growTo(int newCapacity) {
101 | final oldData = _data;
102 | _data = List.filled(newCapacity, null)
103 | ..setRange(0, oldData.length, oldData);
104 | }
105 |
106 | void _ensureCapacity(int index) {
107 | if (index >= _data.length) {
108 | _growTo(index * 2);
109 | }
110 | }
111 |
112 | /// Removes all of the elements from this bag. The bag will be empty after
113 | /// this call returns.
114 | void clear() {
115 | // null all elements so gc can clean up
116 | for (var i = 0; i < _size; i++) {
117 | _data[i] = null;
118 | }
119 | _size = 0;
120 | }
121 |
122 | /// Add all [items] into this bag.
123 | void addAll(Bag items) => items.forEach(add);
124 |
125 | /// Returns [:true:] iff the [index] is within the capacity of the underlying
126 | /// list.
127 | bool isIndexWithinBounds(int index) => index < capacity;
128 |
129 | @override
130 | Iterator get iterator => _data.sublist(0, size).cast().iterator;
131 |
132 | @override
133 | int get length => size;
134 |
135 | @override
136 | Bag cast() => Bag(capacity: _data.length)
137 | .._size = _size
138 | .._data = _data.cast();
139 | }
140 |
--------------------------------------------------------------------------------
/test/dartemis/core/component_manager_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dartemis/dartemis.dart';
2 | import 'package:test/test.dart';
3 |
4 | import 'components_setup.dart';
5 |
6 | void main() {
7 | group('integration tests for ComponentManager', () {
8 | late World world;
9 | setUp(() {
10 | world = World();
11 | });
12 | test('returns correct bit', () {
13 | final componentManager = world.getManager();
14 | expect(componentManager.getBitIndex(Component0), 0);
15 | expect(componentManager.getBitIndex(Component1), 1);
16 | expect(componentManager.getBitIndex(PooledComponent2), 2);
17 | });
18 | test('ComponentManager correctly associates entity and components', () {
19 | final entity = world.createEntity();
20 | final componentA = Component0();
21 | final componentC = PooledComponent2();
22 | world.addComponents(entity, [componentA, componentC]);
23 |
24 | final components = world.getComponents(entity);
25 |
26 | expect(components, containsAll([componentA, componentC]));
27 | expect(components.length, equals(2));
28 | });
29 | test('ComponentManager correctly associates multiple entity and components',
30 | () {
31 | final entity1 = world.createEntity();
32 | final component1A = Component0();
33 | final component1C = PooledComponent2();
34 | world
35 | ..addComponent(entity1, component1A)
36 | ..addComponent(entity1, component1C);
37 |
38 | final entity2 = world.createEntity();
39 | final component2A = Component0();
40 | final component2B = Component1();
41 | final component2C = PooledComponent2();
42 | world.addComponents(entity2, [component2A, component2B, component2C]);
43 |
44 | final components1 = world.getComponents(entity1);
45 | final components2 = world.getComponents(entity2);
46 |
47 | expect(components1, containsAll([component1A, component1C]));
48 | expect(components1.length, equals(2));
49 |
50 | expect(components2, containsAll([component2A, component2B, component2C]));
51 | expect(components2.length, equals(3));
52 | });
53 | test('ComponentManager removes Components of deleted entity', () {
54 | final entity = world.createEntity();
55 | final componentA = Component0();
56 | final componentC = PooledComponent2();
57 | world
58 | ..addComponents(entity, [componentA, componentC])
59 | ..addEntity(entity)
60 | ..initialize()
61 | ..process()
62 | ..deleteEntity(entity)
63 | ..process();
64 |
65 | final fillBag = world.getComponents(entity);
66 | expect(fillBag.length, equals(0));
67 | });
68 | test('ComponentManager can be created for unused Component', () {
69 | final componentsByType =
70 | world.componentManager.getComponentsByType();
71 | expect(componentsByType.length, equals(0));
72 | });
73 | test('ComponentManager returns specific component for specific entity', () {
74 | final componentA = Component0();
75 | final entity = world.createEntity([componentA]);
76 |
77 | expect(
78 | world.componentManager.getComponent(entity),
79 | equals(componentA),
80 | );
81 | });
82 | test(
83 | 'ComponentManager returns null if component for specific entity '
84 | 'has not been registered', () {
85 | final entity = world.createEntity([Component0()]);
86 |
87 | expect(
88 | world.componentManager.getComponent(entity),
89 | isNull,
90 | );
91 | });
92 | test(
93 | 'ComponentManager returns null if component for specific entity does '
94 | 'not exist', () {
95 | final entity = world.createEntity([Component0()]);
96 | // create an entity with the component we want to access so it gets
97 | // registered with the ComponentManager and a _ComponentInfo to access
98 | // is created
99 | world.createEntity([Component1()]);
100 |
101 | expect(
102 | world.componentManager.getComponent(entity),
103 | isNull,
104 | );
105 | });
106 | test(
107 | 'ComponentManager returns null if no component for high index entity '
108 | 'exist', () {
109 | final componentA = Component0();
110 | world.createEntity([componentA]);
111 | for (var i = 0; i < 1000; i++) {
112 | world.createEntity([]);
113 | }
114 | final highIdEntity = world.createEntity([]);
115 |
116 | expect(
117 | world.componentManager.getComponent(highIdEntity),
118 | isNull,
119 | );
120 | });
121 | });
122 | }
123 |
--------------------------------------------------------------------------------
/lib/src/core/entity_system.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// The most raw entity system. It should not typically be used, but you can
4 | /// create your own entity system handling by extending this. It is recommended
5 | /// that you use the other provided entity system implementations.
6 | ///
7 | /// There is no need to ever call any other method than process on objects of
8 | /// this class.
9 | abstract class EntitySystem {
10 | late final World _world;
11 |
12 | List _actives = [];
13 |
14 | final List _interestingComponentsIndices = [];
15 | final List _componentIndicesAll = [];
16 | final List _componentIndicesOne = [];
17 | final List _componentIndicesExcluded = [];
18 |
19 | final Aspect _aspect;
20 | final BitSet _all = BitSet(64);
21 | final BitSet _excluded = BitSet(64);
22 | final BitSet _one = BitSet(64);
23 |
24 | double _time = 0;
25 | double _delta = 0;
26 | int _frame = 0;
27 |
28 | /// If [passive] is set to true the [EntitySystem] will not be processed by
29 | /// the world.
30 | bool passive;
31 |
32 | /// This [EntitySystem] will only be processed when calling [World.process()]
33 | /// with the same [group].
34 | final int group;
35 |
36 | /// Creates an [EntitySystem] with [aspect].
37 | ///
38 | /// If [passive] is set to [`true`] the system will not be processed as long
39 | /// as it stays passive.
40 | ///
41 | /// If [group] is set, [World.process] needs to be called with this group
42 | /// to be processed. For example the group can be used to handle systems
43 | /// for physics and rendering separately and with different deltas.
44 | EntitySystem(Aspect aspect, {this.passive = false, this.group = 0})
45 | : _aspect = aspect;
46 |
47 | /// Returns the [World] this [EntitySystem] belongs to.
48 | World get world => _world;
49 |
50 | /// Returns how often the systems in this [group] have been processed.
51 | int get frame => _frame;
52 |
53 | /// Returns the time that has elapsed for the systems in this [group] since
54 | /// the game has started (sum of all deltas).
55 | double get time => _time;
56 |
57 | /// Returns the delta that has elapsed since the last update of the world.
58 | double get delta => _delta;
59 |
60 | /// Called before processing of entities begins.
61 | @visibleForOverriding
62 | void begin() {}
63 |
64 | /// This is the only method that is supposed to be called from outside the
65 | /// library,
66 | @visibleForOverriding
67 | void process() {
68 | _frame = world._frame[group]!;
69 | _time = world._time[group]!;
70 | _delta = world.delta;
71 | if (checkProcessing()) {
72 | begin();
73 | processEntities(_actives);
74 | end();
75 | }
76 | }
77 |
78 | /// Called after the processing of entities ends.
79 | @visibleForOverriding
80 | void end() {}
81 |
82 | /// Any implementing entity system must implement this method and the logic
83 | /// to process the given [entities] of the system.
84 | @visibleForOverriding
85 | void processEntities(Iterable entities);
86 |
87 | /// Returns true if the system should be processed, false if not.
88 | @visibleForOverriding
89 | bool checkProcessing() => true;
90 |
91 | /// Override to implement code that gets executed when systems are
92 | /// initialized.
93 | @mustCallSuper
94 | @visibleForOverriding
95 | void initialize(World world) {
96 | _world = world;
97 |
98 | _updateBitMask(_all, _aspect.all);
99 | _updateBitMask(_one, _aspect.one);
100 | _updateBitMask(_excluded, _aspect.excluded);
101 |
102 | _componentIndicesAll.addAll(_all.toIntValues());
103 | _componentIndicesOne.addAll(_one.toIntValues());
104 | _componentIndicesExcluded.addAll(_excluded.toIntValues());
105 | _interestingComponentsIndices.addAll(
106 | _componentIndicesAll
107 | .followedBy(_componentIndicesOne)
108 | .followedBy(_componentIndicesExcluded)
109 | .toList(),
110 | );
111 | }
112 |
113 | void _updateBitMask(BitSet mask, Iterable componentTypes) {
114 | final componentManager = world.getManager();
115 | for (final componentType in componentTypes) {
116 | mask[componentManager.getBitIndex(componentType)] = true;
117 | }
118 | }
119 |
120 | /// Gets called if the world gets destroyed. Override if there is cleanup to
121 | /// do.
122 | @visibleForOverriding
123 | void destroy() {}
124 |
125 | /// Add a [component] to an [entity].
126 | void addComponent(Entity entity, T component) =>
127 | world.addComponent(entity, component);
128 |
129 | /// Remove the component with type [T] from an [entity].
130 | void removeComponent(Entity entity) =>
131 | world.removeComponent(entity);
132 |
133 | /// Delete [entity] from the world.
134 | void deleteFromWorld(Entity entity) => world.deleteEntity(entity);
135 | }
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | dartemis
2 | ========
3 | [](https://github.com/denniskaselow/dartemis/actions/workflows/dart.yml)
4 | [](https://coveralls.io/github/denniskaselow/dartemis?branch=master)
5 | [](https://pub.dartlang.org/packages/dartemis)
6 |
7 | Content
8 | =======
9 | * [About](#about)
10 | * [Getting Started](#getting-started)
11 | * [Documentation](#documentation)
12 | * [Example Games](#example-games-using-dartemis)
13 |
14 | About
15 | =====
16 | **dartemis** is a Dart port of the Entity System Framework **Artemis**.
17 |
18 | The original has been written in Java by Arni Arent and Tiago Costa and can be found here:
19 | [https://gamadu.com/artemis/ (archived)](https://archive.is/1xRWW) with the source available here:
20 | https://code.google.com/p/artemis-framework/
21 |
22 | Ports for other languages are also available:
23 |
24 | * C#: https://github.com/thelinuxlich/artemis_CSharp
25 | * Python: https://github.com/kernhanda/PyArtemis
26 |
27 | Some useful links about what an Entity System/Entity Component System is:
28 |
29 | * [https://piemaster.net/2011/07/entity-component-artemis/ (archived)](https://archive.ph/yGyxW)
30 | * http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/
31 | * https://www.richardlord.net/blog/what-is-an-entity-framework
32 |
33 | Getting started
34 | ===============
35 | 1. Add dartemis to your project by adding it to your **pubspec.yaml**:
36 |
37 | ```yaml
38 | dependencies:
39 | dartemis: any
40 | ```
41 |
42 | 2. Import it in your project:
43 |
44 | ```dart
45 | import 'package:dartemis/dartemis.dart';
46 | ```
47 | 3. Create a world:
48 |
49 | ```dart
50 | final world = World();
51 | ```
52 | 4. Create an entity from a list of components. Entities with different components will be processed by different systems:
53 |
54 | ```dart
55 | world.createEntity([
56 | Position(0, 0),
57 | Velocity(1, 1),
58 | ]);
59 | ```
60 | A `Component` is a pretty simple structure and should not contain any logic:
61 |
62 | ```dart
63 | class Position extends Component {
64 | num x, y;
65 | Position(this.x, this.y);
66 | }
67 | ```
68 | Or if you want to use a `PooledComponent`:
69 |
70 | ```dart
71 | class Position extends PooledComponent {
72 | late num x, y;
73 |
74 | Position._();
75 | factory Position(num x, num y) {
76 | final position = Pooled.of(() => Position._())
77 | ..x = x
78 | ..y = y;
79 | return position;
80 | }
81 | }
82 | ```
83 | By using a factory constructor and calling the static function `Pooled.of`, dartemis is able to reuse destroyed components and they will not be garbage collected.
84 |
85 | 5. Define a systems that should process your entities. The `Aspect` defines which components an entity needs to have in order to be processed by the system:
86 |
87 | ```dart
88 | class MovementSystem extends EntityProcessingSystem {
89 | late Mapper positionMapper;
90 | late Mapper velocityMapper;
91 |
92 | MovementSystem() : super(Aspect.forAllOf([Position, Velocity]));
93 |
94 | void initialize() {
95 | // initialize your system
96 | // Mappers, Systems and Managers have to be assigned here
97 | // see dartemis_builder if you don't want to write this code
98 | positionMapper = Mapper(world);
99 | velocityMapper = Mapper(world);
100 | }
101 |
102 | void processEntity(Entity entity) {
103 | Position position = positionMapper[entity];
104 | Velocity vel = velocityMapper[entity];
105 | position
106 | ..x += vel.x * world.delta
107 | ..y += vel.y * world.delta;
108 | }
109 | }
110 | ```
111 | Or using [dartemis_builder](https://pub.dev/packages/dartemis_builder)
112 |
113 | ```dart
114 | part 'filename.g.part';
115 |
116 | @Generate(
117 | EntityProcessingSystem,
118 | allOf: [
119 | Position,
120 | Velocity,
121 | ],
122 | )
123 | class SimpleMovementSystem extends _$SimpleMovementSystem {
124 | @override
125 | void processEntity(Entity entity, Position position, Velocity velocity) {
126 | position
127 | ..x += velocity.x * world.delta
128 | ..y += velocity.y * world.delta;
129 | }
130 | }
131 | ```
132 | 6. Add your system to the world:
133 |
134 | ```dart
135 | world.addSystem(MovementSystem());
136 | ```
137 | 7. Initialize the world:
138 |
139 | ```dart
140 | world.initialize();
141 | ```
142 | 8. Usually your logic requires a delta, so you need to set it in your game loop:
143 |
144 | ```dart
145 | world.delta = delta;
146 | ```
147 | 9. In your game loop you then process your systems:
148 |
149 | ```dart
150 | world.process();
151 | ```
152 |
153 | Documentation
154 | =============
155 | API
156 | ---
157 | [Reference Manual](https://pub.dartlang.org/documentation/dartemis/latest/index.html)
158 |
159 | Example Games using dartemis
160 | ============================
161 | * [darteroids](https://denniskaselow.github.io/dartemis/example/darteroids/web/darteroids.html) - Very simple example included in the example folder of dartemis, ([Source](https://github.com/denniskaselow/dartemis/tree/master/example/web))
162 | * [Shapeocalypse](https://isowosi.itch.io/shapeocalypse/) - A fast paced reaction game using
163 | Angular, WebAudio and WebGL
164 | * [damacreat](https://isowosi.itch.io/damacreat) - An iogame similar to agar.io about creatures
165 | made of dark matter (circles) consuming dark energy (circles) and other dark matter creatures (circles), which can shoot black holes (circles)
166 |
--------------------------------------------------------------------------------
/example/darteroids/gamelogic_systems.dart:
--------------------------------------------------------------------------------
1 | part of '../main.dart';
2 |
3 | class MovementSystem extends EntityProcessingSystem {
4 | late final Mapper positionMapper;
5 | late final Mapper velocityMapper;
6 |
7 | MovementSystem() : super(Aspect(allOf: [Position, Velocity]));
8 |
9 | @override
10 | void initialize(World world) {
11 | super.initialize(world);
12 | positionMapper = Mapper(world);
13 | velocityMapper = Mapper(world);
14 | }
15 |
16 | @override
17 | void processEntity(Entity entity) {
18 | final pos = positionMapper[entity];
19 | final vel = velocityMapper[entity];
20 |
21 | pos
22 | ..x += vel.x * world.delta / 10.0
23 | ..y += vel.y * world.delta / 10.0;
24 | }
25 | }
26 |
27 | class BulletSpawningSystem extends EntityProcessingSystem {
28 | static const num bulletSpeed = 2.5;
29 |
30 | late final Mapper positionMapper;
31 | late final Mapper cannonMapper;
32 | late final Mapper velocityMapper;
33 |
34 | BulletSpawningSystem() : super(Aspect(allOf: [Cannon, Position, Velocity]));
35 |
36 | @override
37 | void initialize(World world) {
38 | super.initialize(world);
39 | positionMapper = Mapper(world);
40 | cannonMapper = Mapper(world);
41 | velocityMapper = Mapper(world);
42 | }
43 |
44 | @override
45 | void processEntity(Entity entity) {
46 | final cannon = cannonMapper[entity];
47 |
48 | if (cannon.canShoot) {
49 | final pos = positionMapper[entity];
50 | final vel = velocityMapper[entity];
51 | fireBullet(pos, vel, cannon);
52 | } else if (cannon.cooldown > 0) {
53 | cannon.cooldown -= world.delta;
54 | }
55 | }
56 |
57 | void fireBullet(Position shooterPos, Velocity shooterVel, Cannon cannon) {
58 | cannon.cooldown = 1000;
59 | final dirX = cannon.targetX - shooterPos.x;
60 | final dirY = cannon.targetY - shooterPos.y;
61 | final distance = sqrt(pow(dirX, 2) + pow(dirY, 2));
62 | final velX = shooterVel.x + bulletSpeed * (dirX / distance);
63 | final velY = shooterVel.y + bulletSpeed * (dirY / distance);
64 |
65 | world.createEntity([
66 | Position(shooterPos.x, shooterPos.y),
67 | Velocity(velX, velY),
68 | CircularBody(2, 'red'),
69 | Decay(5000),
70 | AsteroidDestroyer(),
71 | ]);
72 | }
73 | }
74 |
75 | class DecaySystem extends EntityProcessingSystem {
76 | late final Mapper decayMapper;
77 |
78 | DecaySystem() : super(Aspect(allOf: [Decay]));
79 |
80 | @override
81 | void initialize(World world) {
82 | super.initialize(world);
83 | decayMapper = Mapper(world);
84 | }
85 |
86 | @override
87 | void processEntity(Entity entity) {
88 | final decay = decayMapper[entity];
89 |
90 | if (decay.timer < 0) {
91 | world.deleteEntity(entity);
92 | } else {
93 | decay.timer -= world.delta;
94 | }
95 | }
96 | }
97 |
98 | class AsteroidDestructionSystem extends EntityProcessingSystem {
99 | static final num sqrtOf2 = sqrt(2);
100 | late final GroupManager groupManager;
101 | late final Mapper positionMapper;
102 | late final Mapper bodyMapper;
103 |
104 | AsteroidDestructionSystem()
105 | : super(Aspect(allOf: [AsteroidDestroyer, Position]));
106 |
107 | @override
108 | void initialize(World world) {
109 | super.initialize(world);
110 | positionMapper = Mapper(world);
111 | bodyMapper = Mapper(world);
112 | groupManager = world.getManager();
113 | }
114 |
115 | @override
116 | void processEntity(Entity entity) {
117 | final destroyerPos = positionMapper[entity];
118 |
119 | for (final asteroid in groupManager.getEntities(groupAsteroids)) {
120 | final asteroidPos = positionMapper[asteroid];
121 | final asteroidBody = bodyMapper[asteroid];
122 |
123 | if (doCirclesCollide(
124 | destroyerPos.x,
125 | destroyerPos.y,
126 | 0,
127 | asteroidPos.x,
128 | asteroidPos.y,
129 | asteroidBody.radius,
130 | )) {
131 | deleteFromWorld(asteroid);
132 | deleteFromWorld(entity);
133 | if (asteroidBody.radius > 10) {
134 | createNewAsteroids(asteroidPos, asteroidBody);
135 | createNewAsteroids(asteroidPos, asteroidBody);
136 | }
137 | }
138 | }
139 | }
140 |
141 | void createNewAsteroids(Position asteroidPos, CircularBody asteroidBody) {
142 | final vx = generateRandomVelocity();
143 | final vy = generateRandomVelocity();
144 | final radius = asteroidBody.radius / sqrtOf2;
145 |
146 | final asteroid = world.createEntity([
147 | Position(asteroidPos.x, asteroidPos.y),
148 | Velocity(vx, vy),
149 | CircularBody(radius, asteroidColor),
150 | PlayerDestroyer(),
151 | ]);
152 |
153 | groupManager.add(asteroid, groupAsteroids);
154 | }
155 | }
156 |
157 | class PlayerCollisionDetectionSystem extends EntitySystem {
158 | late final TagManager tagManager;
159 | late final Mapper statusMapper;
160 | late final Mapper positionMapper;
161 | late final Mapper bodyMapper;
162 |
163 | PlayerCollisionDetectionSystem()
164 | : super(Aspect(allOf: [PlayerDestroyer, Position, CircularBody]));
165 |
166 | @override
167 | void initialize(World world) {
168 | super.initialize(world);
169 | positionMapper = Mapper(world);
170 | statusMapper = Mapper(world);
171 | bodyMapper = Mapper(world);
172 | tagManager = world.getManager();
173 | }
174 |
175 | @override
176 | void processEntities(Iterable entities) {
177 | final player = tagManager.getEntity(tagPlayer)!;
178 | final playerPos = positionMapper[player];
179 | final playerStatus = statusMapper[player];
180 | final playerBody = bodyMapper[player];
181 |
182 | if (!playerStatus.invisible) {
183 | for (final entity in entities) {
184 | final pos = positionMapper[entity];
185 | final body = bodyMapper[entity];
186 |
187 | if (doCirclesCollide(
188 | pos.x,
189 | pos.y,
190 | body.radius,
191 | playerPos.x,
192 | playerPos.y,
193 | playerBody.radius,
194 | )) {
195 | playerStatus.lifes--;
196 | playerStatus.invisiblityTimer = 5000;
197 | playerPos
198 | ..x = maxWidth ~/ 2
199 | ..y = maxHeight ~/ 2;
200 | return;
201 | }
202 | }
203 | } else {
204 | playerStatus.invisiblityTimer -= world.delta;
205 | }
206 | }
207 |
208 | @override
209 | bool checkProcessing() => true;
210 | }
211 |
--------------------------------------------------------------------------------
/lib/src/core/utils/bit_set.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | /// [BitSet] to store bits.
4 | class BitSet {
5 | Uint32List _data;
6 | int _length;
7 |
8 | /// Creates a [BitSet] with maximum [length] items.
9 | ///
10 | /// [length] will be rounded up to match the 32-bit boundary.
11 | factory BitSet(int length) => BitSet._(Uint32List(_bufferLength32(length)));
12 |
13 | /// Creates a [BitSet] using an existing [BitSet].
14 | factory BitSet.fromBitSet(BitSet set, {int? length}) {
15 | length ??= set.length;
16 | final data = Uint32List(_bufferLength32(length))
17 | ..setRange(0, set._data.length, set._data);
18 | return BitSet._(data);
19 | }
20 |
21 | BitSet._(this._data) : _length = _data.length << 5;
22 |
23 | /// The value of the bit with the specified [index].
24 | bool operator [](int index) =>
25 | (_data[index >> 5] & _bitMask[index & 0x1f]) != 0;
26 |
27 | /// Sets the bit specified by the [index] to the [value].
28 | void operator []=(int index, bool value) {
29 | if (value) {
30 | _data[index >> 5] |= _bitMask[index & 0x1f];
31 | } else {
32 | _data[index >> 5] &= _clearMask[index & 0x1f];
33 | }
34 | }
35 |
36 | /// The number of bit in this [BitSet].
37 | ///
38 | /// [length] will be rounded up to match the 32-bit boundary.
39 | ///
40 | /// The valid index values for the array are `0` through `length - 1`.
41 | int get length => _length;
42 |
43 | /// The number of bits set to true.
44 | int get cardinality => _data.buffer
45 | .asUint8List()
46 | .fold(0, (sum, value) => sum + _cardinalityBitCounts[value]);
47 |
48 | /// Whether the [BitSet] is empty == has only zero values.
49 | bool get isEmpty => _data.every((i) => i == 0);
50 |
51 | /// Whether the [BitSet] is not empty == has set values.
52 | bool get isNotEmpty => _data.any((i) => i != 0);
53 |
54 | /// Sets all of the bits in the current [BitSet] to true.
55 | void setAll() {
56 | for (var i = 0; i < _data.length; i++) {
57 | _data[i] = 0xffffffff;
58 | }
59 | }
60 |
61 | /// Sets all of the bits in the current [BitSet] to false.
62 | void clearAll() {
63 | for (var i = 0; i < _data.length; i++) {
64 | _data[i] = 0;
65 | }
66 | }
67 |
68 | /// Update the current [BitSet] using a logical AND operation with the
69 | /// corresponding elements in the specified [other].
70 | void and(BitSet other) {
71 | var i = 0;
72 | for (; i < _data.length && i < other._data.length; i++) {
73 | _data[i] &= other._data[i];
74 | }
75 | for (; i < _data.length; i++) {
76 | _data[i] = 0;
77 | }
78 | }
79 |
80 | /// Update the current [BitSet] using a logical OR operation with the
81 | /// corresponding elements in the specified [other].
82 | void or(BitSet other) {
83 | if (other._data.length > _data.length) {
84 | _data = Uint32List(other.length)..setRange(0, _data.length, _data);
85 | _length = other.length;
86 | }
87 | var i = 0;
88 | for (; i < _data.length && i < other._data.length; i++) {
89 | _data[i] |= other._data[i];
90 | }
91 | for (; i < other._data.length; i++) {
92 | _data[i] = other._data[i];
93 | }
94 | }
95 |
96 | /// Update the current [BitSet] using a logical AND NOT operation with the
97 | /// corresponding elements in the specified [other].
98 | void andNot(BitSet other) {
99 | var i = 0;
100 | for (; i < _data.length && i < other._data.length; i++) {
101 | // ignore: unnecessary_parenthesis
102 | _data[i] &= ~(other._data[i]);
103 | }
104 | }
105 |
106 | /// Creates a copy of the current [BitSet].
107 | BitSet _clone() =>
108 | BitSet._(Uint32List(_data.length)..setRange(0, _data.length, _data));
109 |
110 | /// Creates a [BitSet] using a logical AND operation with the
111 | /// corresponding elements in the specified [other].
112 | /// Length of [other] has to be the same.
113 | BitSet operator &(BitSet other) => _clone()..and(other);
114 |
115 | /// Not implemented
116 | BitSet operator %(BitSet set) =>
117 | throw UnimplementedError('andNot not implemented');
118 |
119 | /// Creates a [BitSet] using a logical OR operation with the
120 | /// corresponding elements in the specified [other].
121 | /// Length of [other] has to be the same.
122 | BitSet operator |(BitSet other) => _clone()..or(other);
123 |
124 | /// Not implemented
125 | BitSet operator ^(BitSet other) =>
126 | throw UnimplementedError('xor not implemented');
127 |
128 | @override
129 | String toString() {
130 | final sb = StringBuffer();
131 | for (var i = 0; i < length; i++) {
132 | sb.write(this[i] ? '1' : '0');
133 | }
134 | return sb.toString();
135 | }
136 |
137 | @override
138 | // ignore: type_annotate_public_apis
139 | bool operator ==(other) {
140 | if (identical(this, other)) {
141 | return true;
142 | }
143 | if (other is BitSet && runtimeType == other.runtimeType) {
144 | return equals(other);
145 | }
146 | return false;
147 | }
148 |
149 | /// Compares two bitsets.
150 | bool equals(BitSet other) {
151 | if (length == other.length) {
152 | for (var i = 0; i < _data.length; i++) {
153 | if (_data[i] != other._data[i]) {
154 | return false;
155 | }
156 | }
157 | return true;
158 | }
159 | return false;
160 | }
161 |
162 | @override
163 | int get hashCode => _data.hashCode ^ _length.hashCode;
164 |
165 | static int _bufferLength32(int length) => 1 + (length - 1) ~/ 32;
166 |
167 | /// Returns the set indices.
168 | List toIntValues() {
169 | final result = [];
170 | var index = 0;
171 | for (var value in _data) {
172 | for (var i = 0; i < 4; i++) {
173 | result.addAll(
174 | _indices[value & 0xff].map((internalValue) => internalValue + index),
175 | );
176 | index += 8;
177 | value = value >> 8;
178 | }
179 | }
180 | return result;
181 | }
182 | }
183 |
184 | final _bitMask = List.generate(32, (i) => 1 << i);
185 | final _clearMask = List.generate(32, (i) => ~(1 << i));
186 | final _cardinalityBitCounts = List.generate(256, _cardinalityOfByte);
187 | int _cardinalityOfByte(int index) {
188 | var result = 0;
189 | var value = index;
190 | while (value > 0) {
191 | if (value & 0x01 != 0) {
192 | result++;
193 | }
194 | value = value >> 1;
195 | }
196 | return result;
197 | }
198 |
199 | final _indices = List>.generate(256, _indicesOfByte);
200 | List _indicesOfByte(int index) {
201 | final result = [];
202 | var value = index;
203 | var count = 0;
204 | while (value > 0) {
205 | if (value & 0x01 != 0) {
206 | result.add(count);
207 | }
208 | count++;
209 | value = value >> 1;
210 | }
211 | return result;
212 | }
213 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | errors:
3 | unused_element: error
4 | unused_import: error
5 | unused_local_variable: error
6 | dead_code: error
7 | language:
8 | strict-casts: true
9 | strict-inference: true
10 | strict-raw-types: true
11 | linter:
12 | rules:
13 | # http://dart-lang.github.io/linter/lints/options/options.html
14 | - always_declare_return_types
15 | - always_put_control_body_on_new_line
16 | - always_put_required_named_parameters_first
17 | # - always_specify_types
18 | # - always_use_package_imports
19 | - annotate_overrides
20 | - avoid_annotating_with_dynamic
21 | - avoid_bool_literals_in_conditional_expressions
22 | - avoid_catches_without_on_clauses
23 | - avoid_catching_errors
24 | # - avoid_classes_with_only_static_members
25 | - avoid_double_and_int_checks
26 | - avoid_dynamic_calls
27 | - avoid_empty_else
28 | # - avoid_equals_and_hash_code_on_mutable_classes
29 | - avoid_escaping_inner_quotes
30 | - avoid_field_initializers_in_const_classes
31 | # - avoid_final_parameters
32 | - avoid_function_literals_in_foreach_calls
33 | - avoid_implementing_value_types
34 | - avoid_init_to_null
35 | - avoid_js_rounded_ints
36 | - avoid_multiple_declarations_per_line
37 | - avoid_null_checks_in_equality_operators
38 | - avoid_positional_boolean_parameters
39 | - avoid_print
40 | - avoid_private_typedef_functions
41 | - avoid_redundant_argument_values
42 | - avoid_relative_lib_imports
43 | - avoid_renaming_method_parameters
44 | - avoid_return_types_on_setters
45 | - avoid_returning_null_for_void
46 | - avoid_returning_this
47 | - avoid_setters_without_getters
48 | - avoid_shadowing_type_parameters
49 | - avoid_single_cascade_in_expression_statements
50 | - avoid_slow_async_io
51 | - avoid_type_to_string
52 | - avoid_types_as_parameter_names
53 | - avoid_types_on_closure_parameters
54 | - avoid_unnecessary_containers
55 | - avoid_unused_constructor_parameters
56 | - avoid_void_async
57 | - avoid_web_libraries_in_flutter
58 | - await_only_futures
59 | - camel_case_extensions
60 | - camel_case_types
61 | - cancel_subscriptions
62 | - cascade_invocations
63 | - cast_nullable_to_non_nullable
64 | - close_sinks
65 | - collection_methods_unrelated_type
66 | - combinators_ordering
67 | - comment_references
68 | - conditional_uri_does_not_exist
69 | - constant_identifier_names
70 | - control_flow_in_finally
71 | - curly_braces_in_flow_control_structures
72 | - dangling_library_doc_comments
73 | - depend_on_referenced_packages
74 | - deprecated_consistency
75 | - deprecated_member_use_from_same_package
76 | - diagnostic_describe_all_properties
77 | - directives_ordering
78 | - discarded_futures
79 | - do_not_use_environment
80 | - empty_catches
81 | - empty_constructor_bodies
82 | - empty_statements
83 | - eol_at_end_of_file
84 | - exhaustive_cases
85 | - file_names
86 | - flutter_style_todos
87 | - hash_and_equals
88 | - implementation_imports
89 | - implicit_call_tearoffs
90 | - implicit_reopen
91 | - invalid_case_patterns
92 | - join_return_with_assignment
93 | - leading_newlines_in_multiline_strings
94 | - library_annotations
95 | - library_names
96 | - library_prefixes
97 | - library_private_types_in_public_api
98 | - lines_longer_than_80_chars
99 | - literal_only_boolean_expressions
100 | - matching_super_parameters
101 | - missing_whitespace_between_adjacent_strings
102 | - no_adjacent_strings_in_list
103 | - no_default_cases
104 | - no_duplicate_case_values
105 | - no_leading_underscores_for_library_prefixes
106 | - no_leading_underscores_for_local_identifiers
107 | - no_literal_bool_comparisons
108 | - no_logic_in_create_state
109 | - no_runtimeType_toString
110 | - no_self_assignments
111 | - non_constant_identifier_names
112 | - noop_primitive_operations
113 | - null_check_on_nullable_type_parameter
114 | - null_closures
115 | - omit_local_variable_types
116 | - one_member_abstracts
117 | - only_throw_errors
118 | - overridden_fields
119 | - package_names
120 | - package_prefixed_library_names
121 | - parameter_assignments
122 | - prefer_adjacent_string_concatenation
123 | - prefer_asserts_in_initializer_lists
124 | - prefer_asserts_with_message
125 | - prefer_collection_literals
126 | - prefer_conditional_assignment
127 | - prefer_const_constructors
128 | - prefer_const_constructors_in_immutables
129 | - prefer_const_declarations
130 | - prefer_const_literals_to_create_immutables
131 | - prefer_constructors_over_static_methods
132 | - prefer_contains
133 | # - prefer_double_quotes
134 | - prefer_expression_function_bodies
135 | - prefer_final_fields
136 | - prefer_final_in_for_each
137 | - prefer_final_locals
138 | # - prefer_final_parameters
139 | - prefer_for_elements_to_map_fromIterable
140 | - prefer_foreach
141 | - prefer_function_declarations_over_variables
142 | - prefer_generic_function_type_aliases
143 | - prefer_if_elements_to_conditional_expressions
144 | - prefer_if_null_operators
145 | - prefer_initializing_formals
146 | - prefer_inlined_adds
147 | - prefer_int_literals
148 | - prefer_interpolation_to_compose_strings
149 | - prefer_is_empty
150 | - prefer_is_not_empty
151 | - prefer_is_not_operator
152 | - prefer_iterable_whereType
153 | - prefer_mixin
154 | - prefer_null_aware_method_calls
155 | - prefer_null_aware_operators
156 | - prefer_relative_imports
157 | - prefer_single_quotes
158 | - prefer_spread_collections
159 | - prefer_typing_uninitialized_variables
160 | - prefer_void_to_null
161 | - provide_deprecation_message
162 | - public_member_api_docs
163 | - recursive_getters
164 | - require_trailing_commas
165 | - secure_pubspec_urls
166 | - sized_box_for_whitespace
167 | - sized_box_shrink_expand
168 | - slash_for_doc_comments
169 | - sort_child_properties_last
170 | # - sort_constructors_first
171 | - sort_pub_dependencies
172 | - sort_unnamed_constructors_first
173 | - test_types_in_equals
174 | - throw_in_finally
175 | - tighten_type_of_initializing_formals
176 | - type_annotate_public_apis
177 | - type_init_formals
178 | - type_literal_in_constant_pattern
179 | - unawaited_futures
180 | - unnecessary_await_in_return
181 | - unnecessary_brace_in_string_interps
182 | - unnecessary_breaks
183 | - unnecessary_const
184 | - unnecessary_constructor_name
185 | # - unnecessary_final
186 | - unnecessary_getters_setters
187 | - unnecessary_lambdas
188 | - unnecessary_late
189 | - unnecessary_library_directive
190 | - unnecessary_new
191 | - unnecessary_null_aware_assignments
192 | - unnecessary_null_aware_operator_on_extension_on_nullable
193 | - unnecessary_null_checks
194 | - unnecessary_null_in_if_null_operators
195 | - unnecessary_nullable_for_final_variable_declarations
196 | - unnecessary_overrides
197 | - unnecessary_parenthesis
198 | - unnecessary_raw_strings
199 | - unnecessary_statements
200 | - unnecessary_string_escapes
201 | - unnecessary_string_interpolations
202 | - unnecessary_this
203 | - unnecessary_to_list_in_spreads
204 | - unreachable_from_main
205 | - unrelated_type_equality_checks
206 | - use_build_context_synchronously
207 | - use_colored_box
208 | - use_decorated_box
209 | - use_enums
210 | - use_full_hex_values_for_flutter_colors
211 | - use_function_type_syntax_for_parameters
212 | - use_if_null_to_convert_nulls_to_bools
213 | - use_is_even_rather_than_modulo
214 | - use_key_in_widget_constructors
215 | - use_late_for_private_fields_and_variables
216 | - use_named_constants
217 | - use_raw_strings
218 | - use_rethrow_when_possible
219 | - use_setters_to_change_properties
220 | - use_string_buffers
221 | - use_string_in_part_of_directives
222 | - use_super_parameters
223 | - use_test_throws_matchers
224 | - use_to_and_as_if_applicable
225 | - valid_regexps
226 | - void_checks
227 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | ## 0.10.0
3 |
4 | ### Breaking API Changes
5 | * entities are no longer simple `int`s and have been turned into an `extension type Entity(int)`.
6 | Methods that previously expected `int entity` or `Iterable entities>`
7 | now expect `Entity entity` or `Iterable entities`
8 | * removed named parameters `group` and `passive` from `World.addSystem`,
9 | they are now named parameters of the constructor of `EntitySystem`
10 | * the `initialize`-method of `Manager`s and `EntitySystem`s now has a
11 | parameter for the `World` and must be called when overriding `initialize`
12 | * it's no longer possible to add systems or managers after the world has been initialized
13 | * `ComponentType` has been turned into an extension type, static methods of this class are now instance methods on
14 | `ComponentManager`
15 | * combined the different `Aspect`-constructors into a single one with optional named parameters for `allOf`, `oneOf`
16 | and `exclude`
17 |
18 | ### Enhancements
19 | * it's now possible to have multiple worlds (e.g. multiple games in the same webpage/app)
20 | * sharing instances of components/entities/managers/systems between worlds is NOT possible and things will break
21 | * added `@visibleForOverriding`-annotations to several methods that are only supposed to be called by dartemis
22 |
23 | ## 0.9.9
24 | ### Enhancements
25 | * new method `getTag` in `TagManager` to get the tag of an entity
26 |
27 | ## 0.9.8+1
28 | ### Bugfix
29 | * fix crash when moving components
30 |
31 | ## 0.9.8
32 | ### Enhancements
33 | * new method in `World` to move a component from one entity to another
34 |
35 | ## 0.9.7
36 | ### Enhancements
37 | * `EntitySystem.checkProcessing()` is no longer abstract, returns `true`
38 | * delta can be directly accessed in systems (**BREAKING CHANGE** if variable delta already exists in extending system)
39 |
40 | ### Internal
41 | * use SDK 2.17 (super parameters, constructor tear-offs)
42 |
43 | ## 0.9.6
44 | ### Enhancements
45 | * performance improvement when adding/removing components
46 |
47 | ## 0.9.5+3
48 | ### Bugfix
49 | * `componentManager.getComponent` couldn't handle accessing components that don't exist for the
50 | specific entity
51 |
52 | ## 0.9.5+2
53 | ### Documentation
54 | * updated links and minor changes to the example code in README.md
55 |
56 | ## 0.9.5+1
57 | ### Bugfix
58 | * `componentManager.getComponent` couldn't handle accessing components if no high index entities
59 | with those components have been created
60 |
61 | ## 0.9.5
62 | ### Enhancements
63 | * allow direct access to a specific component without use of mappers via `world.componentManager.getComponent(int entity, ComponentType componentType)`
64 |
65 | ## 0.9.4
66 | ### Bugfix
67 | * don't update the active entities in a systems if nothing changed
68 | * don't allow multiple instances of the same system or manager
69 |
70 | ## 0.9.3
71 | ### Bugfix
72 | * process deleted entities after a system finishes in case the system interacts multiple times with the deleted entity
73 |
74 | ## 0.9.2
75 | ### Bugfix
76 | * handle more than 32 entities with the same components
77 |
78 | ## 0.9.1
79 | ### Bugfix
80 | * handle more than 32 components when adding systems for components that haven't been used yet
81 |
82 | ## 0.9.0
83 | ### Internal
84 | * updated dependencies to stable versions
85 |
86 | ## 0.9.0-nullsafety.0
87 | ### Breaking API Changes
88 | * removed `ComponentTypeManager` and moved methods to `ComponentType`
89 | * `getComponents*` methods in `ComponentManager` now return a `List` instead of a `Bag`
90 |
91 | ### Enhancements
92 | * switched to NNBD mode
93 | * added `OptionalMapper` with a nullable return type for the `[]` operator
94 |
95 | ## 0.8.0 (Dart 2.0+ required)
96 | ### Breaking API Changes
97 | * removed deprecated code
98 | * replaced `Entity` with `int`, methods previously on `Entity` need to be called on `World`, with the `int` value of the entity as the first parameter
99 | * removed `world.processEntityChanges`, it's now done by default after every system
100 | * `Aspect` no longer uses static methods, uses named constructors instead
101 | (migration: replace `Aspect.getAspectF` with `Aspect.f`)
102 | * methods in `Aspect` no longer return the aspect, use cascading operator to chain calls
103 | * improved type safety for `world.getManager` and `world.getSystem`, no longer takes a `Type` as parameter and uses
104 | generic methods instead (e.g. `world.getManager()` instead of `world.getManager(TagManager)`)
105 | * removed `Type` parameter in the constructor of `Mapper`, change code from `Mapper(Position, world)` to `Mapper(world)`
106 |
107 | ### Enhancements
108 | * `world.destroy()` for cleaning up `EntitySystem`s and `Manager`s
109 |
110 | ### Internal
111 | * existing entities are processed first, addition of new entities is processed last, makes more sense now that the
112 | processing is done after every system
113 |
114 | ## 0.7.3
115 | ### Bugfixes
116 | * adding an entity to a dirty EntityBag could lead to an inconsistency between the bitset and the list of entities
117 |
118 | ## 0.7.2
119 | ### Bugfixes
120 | * removing an entity multiple times caused it to be added to the entity pool multiple times
121 |
122 | ## 0.7.1
123 | ### Internal
124 | * upgraded dependencies
125 |
126 | ## 0.7.0
127 | ### Breaking API Changes
128 | * renamed `Poolable` to `Pooled`
129 | * renamed `ComponentPoolable` to `PooledComponent`
130 | * removed `FastMath` and `Utils`, unrelated to ECS
131 | * removed `removeAll` from `Bag`
132 | * `time` and `frame` getters have been moved from `World` to `EntitySystem`, `World` has methods instead
133 | ### API Changes
134 | * deprecated `ComponentMapper` use `Mapper` instead
135 | * deprecated `ComponentMapper#get(Entity)`, use `Mapper[Entity]` instead
136 | * properties have been added to the `World`, can be accessed using the `[]` operator
137 | * `System`s can be assigned to a group when adding them to the `World`, `Word.process()` can be called for a specific group
138 | ### Enhancements
139 | * performance improvement when removing entities
140 | ### Bugfixes
141 | * DelayedEntityProcessingSystem keeps running until all current entities have expired
142 | ### Internal
143 | * upgraded dependencies
144 |
145 | ## 0.6.0
146 | ### API Changes
147 | * `Bag` is `Iterable`
148 | * removed `ReadOnlyBag`, when upgrading to 0.6.0 replace every occurence of `ReadOnlyBag` with `Iterable`
149 |
150 | ## 0.5.2
151 | ### Enhancements
152 | * injection works for `Manager`s
153 | * `initialize()` in the `Manager` is no longer abstract (same as in `EntitySystem`)
154 | * `World.createEntity` got an optional paramter to create an `Entity` with components
155 | * new function `World.createAndAddEntity` which adds the `Entity` to the world after creation
156 |
157 | ### Bugfixes
158 | * added getter for the `World` in `Manager`
159 | * the uniqueId of an `Entity` was always 0, not very unique
160 |
161 | ## 0.5.1
162 | ### Internal
163 | * added version constraint for release of Dart
164 |
165 | ## 0.5.0
166 | ### Enhancements
167 | * more injection, less boilerplate (when using dartemis_mirrors)
168 | * Instances of `ComponentMapper` no longer need to be created in the `initialize`-method of a system, they will be injected
169 | * `Manager`s and `EntitySystem`s no longer need to be requested from the `World` in the `initialize`-method of a system, they will be injected
170 |
171 | ## 0.4.2
172 | ### Bugfixes
173 | * `EntityManager.isEnabled()` no longer fails if the bag of disabled entities is smaller than the id of the checked entity
174 |
175 | ### Enhancements
176 | * added getters for current `time` and `frame` to `World`
177 |
178 | ## 0.4.1
179 | ### Bugfixes
180 | * `World.deleteAllEntites()` did not work if there was already a deleted entity
181 | * writing to the `Bag` by index doesn't make it smaller anymore
182 |
183 | ## 0.4.0
184 | ### API Changes
185 | * swapped parameters of `Tagmanager.register`
186 | * replaced `ImmutableBag` with `ReadOnlyBag`, added getter for `ReadOnlyBag` to `Bag`
187 | * changed `FreeComponents` to `ObjectPool`
188 | * old `Component` has changed, there are two different kinds of components now:
189 | * instances of classes extending `ComponentPoolable` will be added to the `ObjectPool` when they are removed from an `Entity` (preventing garbage collection and allowing reuse)
190 | * instances of classes extending `Component` will not be added to the `ObjectPool` when they are removed from an `Entity` (allowing garbage collection)
191 |
192 | ### Enhancements
193 | * added function `deleteAllEntities` to `World`
194 | * `IntervalEntitySystem` has a getter for the `delta` since the systm was processed last
195 | * updated to work with Dart M4
196 |
197 | ### Bugfixes
198 | * `GroupManager.isInGroup` works if entity is in no group
199 |
--------------------------------------------------------------------------------
/lib/src/core/component_manager.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// Manages als components of all entities.
4 | class ComponentManager extends Manager {
5 | final Bag<_ComponentInfo> _componentInfoByType;
6 | final _componentTypes = {};
7 | final _systemIndices = {};
8 | int _systemCount = 0;
9 | int _componentTypeCount = 0;
10 |
11 | ComponentManager._internal() : _componentInfoByType = Bag<_ComponentInfo>();
12 |
13 | /// Register a system to know if it needs to be updated when an entity
14 | /// changed.
15 | @visibleForTesting
16 | void registerSystem(EntitySystem system) {
17 | final systemBitIndex = _systemIndices.putIfAbsent(
18 | system.runtimeType,
19 | () => _systemCount++,
20 | );
21 | for (final index in system._interestingComponentsIndices) {
22 | _componentInfoByType._ensureCapacity(index);
23 | var componentInfo = _componentInfoByType[index];
24 | if (componentInfo == null) {
25 | componentInfo = _ComponentInfo();
26 | _componentInfoByType[index] = componentInfo;
27 | }
28 |
29 | componentInfo.addInterestedSystem(systemBitIndex);
30 | }
31 | }
32 |
33 | void _unregisterSystem(EntitySystem system) {
34 | final systemBitIndex = _systemIndices.remove(system.runtimeType)!;
35 | for (final index in system._interestingComponentsIndices) {
36 | _componentInfoByType[index]!.removeInterestedSystem(systemBitIndex);
37 | }
38 | }
39 |
40 | /// Removes all components from the [entity].
41 | @visibleForTesting
42 | void removeComponentsOfEntity(Entity entity) {
43 | _forComponentsOfEntity(entity, (components) {
44 | components.remove(entity);
45 | });
46 | }
47 |
48 | void _addComponent(
49 | Entity entity,
50 | T component,
51 | ) {
52 | // needs the runtimeType instead of T because this method gets called
53 | // in a loop over a list of Components, so T would be Component
54 | final type = getTypeFor(component.runtimeType);
55 | final index = type.bitIndex;
56 | _componentInfoByType._ensureCapacity(index);
57 | var componentInfo = _componentInfoByType[index];
58 | if (componentInfo == null) {
59 | componentInfo = _ComponentInfo();
60 | _componentInfoByType[index] = componentInfo;
61 | }
62 | componentInfo[entity] = component;
63 | }
64 |
65 | void _removeComponent(Entity entity) {
66 | final type = getTypeFor(T);
67 | final typeId = type.bitIndex;
68 | _componentInfoByType[typeId]!.remove(entity);
69 | }
70 |
71 | void _moveComponent(Entity entitySrc, Entity entityDst) {
72 | final type = getTypeFor(T);
73 | final typeId = type.bitIndex;
74 | _componentInfoByType[typeId]?.move(entitySrc, entityDst);
75 | }
76 |
77 | /// Returns all components of [ComponentType type] accessible by their entity
78 | /// id.
79 | List _getComponentsByType() {
80 | final type = getTypeFor(T);
81 | final index = type.bitIndex;
82 | _componentInfoByType._ensureCapacity(index);
83 |
84 | var components = _componentInfoByType[index];
85 | if (components == null) {
86 | components = _ComponentInfo();
87 | _componentInfoByType[index] = components;
88 | } else if (components.components is! List) {
89 | // when components get added to an entity as part of a list containing
90 | // multiple different components, the type is infered as Component
91 | // instead of the actual type of the component. So if _addComponent was
92 | // called first a Bag would have been created and this fixes
93 | // the type
94 | _componentInfoByType[index]!.components =
95 | components.components.cast();
96 | components = _componentInfoByType[index];
97 | }
98 |
99 | return components!.components.cast();
100 | }
101 |
102 | /// Returns all components of [ComponentType type].
103 | List getComponentsByType() =>
104 | _getComponentsByType().whereType().toList();
105 |
106 | /// Returns all components of [entity].
107 | List getComponentsFor(Entity entity) {
108 | final result = [];
109 | _forComponentsOfEntity(
110 | entity,
111 | (components) => result.add(components[entity]),
112 | );
113 |
114 | return result;
115 | }
116 |
117 | void _forComponentsOfEntity(
118 | Entity entity,
119 | void Function(_ComponentInfo components) f,
120 | ) {
121 | for (var index = 0; index < _componentTypeCount; index++) {
122 | final componentInfo = _componentInfoByType[index];
123 | if (componentInfo != null &&
124 | componentInfo.entities.length > entity._id &&
125 | componentInfo.entities[entity._id]) {
126 | f(componentInfo);
127 | }
128 | }
129 | }
130 |
131 | /// Returns true if the list of entities of [system] need to be updated.
132 | bool isUpdateNeededForSystem(EntitySystem system) {
133 | final systemBitIndex = _systemIndices[system.runtimeType]!;
134 | for (final interestingComponent in system._interestingComponentsIndices) {
135 | if (_componentInfoByType[interestingComponent]!
136 | .systemRequiresUpdate(systemBitIndex)) {
137 | return true;
138 | }
139 | }
140 | return false;
141 | }
142 |
143 | /// Marks the [system] as updated for the necessary component types.
144 | void _systemUpdated(EntitySystem system) {
145 | final systemBitIndex = _systemIndices[system.runtimeType]!;
146 | for (final interestingComponent in system._interestingComponentsIndices) {
147 | _componentInfoByType[interestingComponent]!.systemUpdated(systemBitIndex);
148 | }
149 | }
150 |
151 | /// Returns every entity that is of interest for [system].
152 | List _getEntitiesForSystem(
153 | EntitySystem system,
154 | int entitiesBitSetLength,
155 | ) {
156 | final baseAll = BitSet(entitiesBitSetLength)..setAll();
157 | for (final interestingComponent in system._componentIndicesAll) {
158 | baseAll.and(_componentInfoByType[interestingComponent]!.entities);
159 | }
160 | final baseOne = BitSet(entitiesBitSetLength);
161 | if (system._componentIndicesOne.isEmpty) {
162 | baseOne.setAll();
163 | } else {
164 | for (final interestingComponent in system._componentIndicesOne) {
165 | baseOne.or(_componentInfoByType[interestingComponent]!.entities);
166 | }
167 | }
168 | final baseExclude = BitSet(entitiesBitSetLength);
169 | for (final interestingComponent in system._componentIndicesExcluded) {
170 | baseExclude.or(_componentInfoByType[interestingComponent]!.entities);
171 | }
172 | baseAll
173 | ..and(baseOne)
174 | ..andNot(baseExclude);
175 | return baseAll.toIntValues().map(Entity._).toList(growable: false);
176 | }
177 |
178 | /// Returns the component of type [T] for the given [entity].
179 | T? getComponent(
180 | Entity entity,
181 | ) {
182 | final componentType = getTypeFor(T);
183 | final index = componentType.bitIndex;
184 | final components = _componentInfoByType[index];
185 | if (components != null && entity._id < components.components.length) {
186 | return components.components[entity._id] as T?;
187 | }
188 | return null;
189 | }
190 |
191 | /// Returns the [ComponentType] for the runtimeType of a [Component].
192 | ComponentType getTypeFor(Type typeOfComponent) => _componentTypes.putIfAbsent(
193 | typeOfComponent,
194 | () => ComponentType(_componentTypeCount++),
195 | );
196 |
197 | /// Returns the index of the bit of the [componentType].
198 | int getBitIndex(Type componentType) => getTypeFor(componentType).bitIndex;
199 | }
200 |
201 | class _ComponentInfo {
202 | BitSet entities = BitSet(32);
203 | List components = List.filled(32, null, growable: true);
204 | BitSet interestedSystems = BitSet(32);
205 | BitSet requiresUpdate = BitSet(32);
206 | bool dirty = false;
207 |
208 | _ComponentInfo();
209 |
210 | void operator []=(Entity entity, T component) {
211 | if (entity._id >= entities.length) {
212 | entities = BitSet.fromBitSet(entities, length: entity._id + 1);
213 | final newCapacity = (entities.length * 3) ~/ 2 + 1;
214 | final filler = List.filled(newCapacity - components.length, null);
215 | components.addAll(filler);
216 | }
217 | entities[entity._id] = true;
218 | components[entity._id] = component;
219 | dirty = true;
220 | }
221 |
222 | T operator [](Entity entity) => components[entity._id]!;
223 |
224 | void remove(Entity entity) {
225 | if (entities.length > entity._id && entities[entity._id]) {
226 | entities[entity._id] = false;
227 | components[entity._id]!._removed();
228 | components[entity._id] = null;
229 | dirty = true;
230 | }
231 | }
232 |
233 | void move(Entity srcEntity, Entity dstEntity) {
234 | if (entities.length > srcEntity._id && entities[srcEntity._id]) {
235 | remove(dstEntity);
236 | this[dstEntity] = components[srcEntity._id]!;
237 | entities[srcEntity._id] = false;
238 | components[srcEntity._id] = null;
239 | dirty = true;
240 | }
241 | }
242 |
243 | void addInterestedSystem(int systemBitIndex) {
244 | if (systemBitIndex >= interestedSystems.length) {
245 | interestedSystems =
246 | BitSet.fromBitSet(interestedSystems, length: systemBitIndex + 1);
247 | requiresUpdate =
248 | BitSet.fromBitSet(requiresUpdate, length: systemBitIndex + 1);
249 | }
250 | interestedSystems[systemBitIndex] = true;
251 | requiresUpdate[systemBitIndex] = true;
252 | }
253 |
254 | void removeInterestedSystem(int systemBitIndex) {
255 | interestedSystems[systemBitIndex] = false;
256 | requiresUpdate[systemBitIndex] = false;
257 | }
258 |
259 | bool systemRequiresUpdate(int systemBitIndex) {
260 | if (dirty) {
261 | requiresUpdate.or(interestedSystems);
262 | dirty = false;
263 | }
264 | return requiresUpdate[systemBitIndex];
265 | }
266 |
267 | void systemUpdated(int systemBitIndex) =>
268 | requiresUpdate[systemBitIndex] = false;
269 |
270 | _ComponentInfo cast() => this as _ComponentInfo;
271 | }
272 |
--------------------------------------------------------------------------------
/lib/src/core/world.dart:
--------------------------------------------------------------------------------
1 | part of '../../dartemis.dart';
2 |
3 | /// The primary instance for the framework. It contains all the managers.
4 | ///
5 | /// You must use this to create, delete and retrieve entities.
6 | ///
7 | /// It is also important to set the delta each game loop iteration, and
8 | /// initialize before game loop.
9 | class World {
10 | final EntityManager _entityManager;
11 | final ComponentManager _componentManager;
12 |
13 | final Map _systems = {};
14 | final List _systemsList = [];
15 |
16 | final Map _managers = {};
17 | final Bag _managersBag = Bag();
18 |
19 | // -1 for triggering deleteEntities when calling process() without processing
20 | // any systems, for testing purposes
21 | final Map _frame = {0: 0, -1: 0};
22 | final Map _time = {0: 0.0, -1: 0.0};
23 | bool _initialized = false;
24 |
25 | final Set _entitiesMarkedForDeletion = {};
26 |
27 | /// The time that passed since the last time [process] was called.
28 | double delta = 0;
29 |
30 | /// World-related properties that can be written and read by the user.
31 | final Map properties = {};
32 |
33 | /// Create the [World] with the default [EntityManager] and
34 | /// [ComponentManager].
35 | World({EntityManager? entityManager, ComponentManager? componentManager})
36 | : _entityManager = entityManager ?? EntityManager._internal(),
37 | _componentManager = componentManager ?? ComponentManager._internal() {
38 | addManager(_entityManager);
39 | addManager(_componentManager);
40 | }
41 |
42 | /// Returns the current frame/how often the systems in [group] have been processed.
43 | int frame([int group = 0]) => _frame[group]!;
44 |
45 | /// Returns the time that has elapsed for the systems in the [group] since
46 | /// the game has started (sum of all deltas).
47 | double time([int group = 0]) => _time[group]!;
48 |
49 | /// Makes sure all managers systems are initialized in the order they were
50 | /// added.
51 | void initialize() {
52 | _managersBag.forEach(_initializeManager);
53 | _systemsList
54 | ..forEach(_initializeSystem)
55 | ..forEach(componentManager.registerSystem);
56 | _initialized = true;
57 | }
58 |
59 | void _initializeManager(Manager manager) => manager.initialize(this);
60 |
61 | void _initializeSystem(EntitySystem system) => system.initialize(this);
62 |
63 | /// Returns a manager that takes care of all the entities in the world.
64 | /// entities of this world.
65 | EntityManager get entityManager => _entityManager;
66 |
67 | /// Returns a manager that takes care of all the components in the world.
68 | ComponentManager get componentManager => _componentManager;
69 |
70 | /// Add a manager into this world. It can be retrieved later. World will
71 | /// notify this manager of changes to entity.
72 | void addManager(Manager manager) {
73 | if (_managers.containsKey(manager.runtimeType)) {
74 | throw ArgumentError.value(
75 | manager,
76 | 'manager',
77 | 'A manager of type "${manager.runtimeType}" has already been added '
78 | 'to the world.');
79 | }
80 | if (_initialized) {
81 | throw StateError(
82 | 'The world has already been initialized. The manager needs to be '
83 | 'added before calling initialize.');
84 | }
85 | _managers[manager.runtimeType] = manager;
86 | _managersBag.add(manager);
87 | }
88 |
89 | /// Returns a [Manager] of the specified type [T].
90 | T getManager() {
91 | final result = _managers[T];
92 | assert(
93 | result != null,
94 | 'No manager of type "$T" has been added to the world.',
95 | );
96 | return result! as T;
97 | }
98 |
99 | /// Deletes the manager from this world.
100 | void deleteManager(Manager manager) {
101 | _managers.remove(manager.runtimeType);
102 | _managersBag.remove(manager);
103 | }
104 |
105 | /// Create and return a new or reused [int] instance, optionally with
106 | /// [components].
107 | Entity createEntity([List components = const []]) {
108 | final e = _entityManager._createEntityInstance();
109 | for (final component in components) {
110 | addComponent(e, component);
111 | }
112 | addEntity(e);
113 | return e;
114 | }
115 |
116 | /// Adds a [component] to the [entity].
117 | void addComponent(Entity entity, T component) =>
118 | componentManager._addComponent(
119 | entity,
120 | component,
121 | );
122 |
123 | /// Adds [components] to the [entity].
124 | void addComponents(Entity entity, List components) {
125 | for (final component in components) {
126 | addComponent(entity, component);
127 | }
128 | }
129 |
130 | /// Removes a [Component] of type [T] from the [entity].
131 | void removeComponent(Entity entity) =>
132 | componentManager._removeComponent(entity);
133 |
134 | /// Moves a [Component] of type [T] from the [srcEntity] to the [dstEntity].
135 | /// if the [srcEntity] does not have the [Component] of type [T] nothing will
136 | /// happen.
137 | void moveComponent(Entity srcEntity, Entity dstEntity) =>
138 | componentManager._moveComponent(
139 | srcEntity,
140 | dstEntity,
141 | );
142 |
143 | /// Gives you all the systems in this world for possible iteration.
144 | Iterable get systems => _systemsList;
145 |
146 | /// Adds a [system] to this world that will be processed by [process()].
147 | void addSystem(EntitySystem system) {
148 | if (_systems.containsKey(system.runtimeType)) {
149 | throw ArgumentError.value(
150 | system,
151 | 'system',
152 | 'A system of type "${system.runtimeType}" has already been added to '
153 | 'the world.');
154 | }
155 | if (_initialized) {
156 | throw StateError(
157 | 'The world has already been initialized. The system needs to be '
158 | 'added before calling initialize.');
159 | }
160 |
161 | _systems[system.runtimeType] = system;
162 | _systemsList.add(system);
163 | _time.putIfAbsent(system.group, () => 0.0);
164 | _frame.putIfAbsent(system.group, () => 0);
165 | }
166 |
167 | /// Removed the specified system from the world.
168 | void deleteSystem(EntitySystem system) {
169 | _systems.remove(system.runtimeType);
170 | _systemsList.remove(system);
171 | componentManager._unregisterSystem(system);
172 | }
173 |
174 | /// Retrieve a system for specified system type.
175 | T getSystem() {
176 | final result = _systems[T];
177 | assert(
178 | result != null,
179 | 'No system of type "$T" has been added to the world.',
180 | );
181 | return result! as T;
182 | }
183 |
184 | /// Processes all changes to entities and executes all non-passive systems.
185 | void process([int group = 0]) {
186 | assert(_frame.containsKey(group), 'No group $group exists');
187 | // delete entites that have been deleted outside of a system
188 | _deleteEntities();
189 | _frame[group] = _frame[group]! + 1;
190 | _time[group] = _time[group]! + delta;
191 |
192 | for (final system in _systemsList
193 | .where((system) => !system.passive && system.group == group)) {
194 | _updateSystem(system);
195 | system.process();
196 |
197 | _deleteEntities();
198 | }
199 | }
200 |
201 | /// Actually delete the entities in the world that have been marked for
202 | /// deletion.
203 | void _deleteEntities() {
204 | _entitiesMarkedForDeletion
205 | ..forEach(_deleteEntity)
206 | ..clear();
207 | }
208 |
209 | /// Delete an entity.
210 | void _deleteEntity(Entity entity) {
211 | for (final manager in _managers.values) {
212 | manager.deleted(entity);
213 | }
214 | componentManager.removeComponentsOfEntity(entity);
215 | entityManager._delete(entity);
216 | }
217 |
218 | void _updateSystem(EntitySystem system) {
219 | if (componentManager.isUpdateNeededForSystem(system)) {
220 | system._actives = componentManager._getEntitiesForSystem(
221 | system,
222 | entityManager._entities.length,
223 | );
224 | componentManager._systemUpdated(system);
225 | }
226 | }
227 |
228 | /// Removes all entities from the world.
229 | ///
230 | /// Every entity and component has to be created anew. Make sure not to reuse
231 | /// [Component]s that were added to an [int] and referenced in you code
232 | /// because they will be added to a free list and might be overwritten once a
233 | /// new [Component] of that type is created.
234 | void deleteAllEntities() {
235 | entityManager._entities
236 | .toIntValues()
237 | .forEach((id) => _entitiesMarkedForDeletion.add(Entity._(id)));
238 | _deleteEntities();
239 | }
240 |
241 | /// Adds a [Entity entity] to this world.
242 | void addEntity(Entity entity) {
243 | entityManager._add(entity);
244 | for (final manager in _managers.values) {
245 | manager.added(entity);
246 | }
247 | }
248 |
249 | /// Mark an [entity] for deletion from the world. Will be deleted after the
250 | /// current system finished running.
251 | void deleteEntity(Entity entity) {
252 | _entitiesMarkedForDeletion.add(entity);
253 | }
254 |
255 | /// Returns the value for [key] from [properties].
256 | Object? operator [](String key) => properties[key];
257 |
258 | /// Set the [value] of [key] in [properties].
259 | void operator []=(String key, Object value) {
260 | properties[key] = value;
261 | }
262 |
263 | /// Destroy the [World] by destroying all [EntitySystem]s and [Manager]s.
264 | void destroy() {
265 | for (final system in _systemsList) {
266 | system.destroy();
267 | }
268 | for (final manager in _managersBag) {
269 | manager.destroy();
270 | }
271 | }
272 |
273 | /// Get all components belonging to this entity.
274 | List getComponents(Entity entity) =>
275 | _componentManager.getComponentsFor(entity);
276 | }
277 |
278 | /// A [World] which measures performance by measureing elapsed time between
279 | /// calls.
280 | @experimental
281 | class PerformanceMeasureWorld extends World {
282 | final int _framesToMeasure;
283 | final Map> _systemTimes = >{};
284 | final Map> _processEntityChangesTimes =
285 | >{};
286 |
287 | /// Create the world and define how many frames should be included when
288 | /// calculating the [PerformanceStats].
289 | PerformanceMeasureWorld(this._framesToMeasure) {
290 | _systemTimes[runtimeType] = ListQueue(_framesToMeasure);
291 | }
292 |
293 | @override
294 | void process([int group = 0]) {
295 | _frame[group] = _frame[group]! + 1;
296 | _time[group] = _time[group]! + delta;
297 | final stopwatch = Stopwatch()..start();
298 | var lastStop = stopwatch.elapsedMicroseconds;
299 | for (final system in _systemsList
300 | .where((system) => !system.passive && system.group == group)) {
301 | _updateSystem(system);
302 | final afterProcessEntityChanges = stopwatch.elapsedMicroseconds;
303 | system.process();
304 | final afterSystem = stopwatch.elapsedMicroseconds;
305 | _storeTime(_systemTimes, system, afterSystem, afterProcessEntityChanges);
306 | _storeTime(
307 | _processEntityChangesTimes,
308 | system,
309 | afterProcessEntityChanges,
310 | lastStop,
311 | );
312 | lastStop = stopwatch.elapsedMicroseconds;
313 | }
314 | final now = stopwatch.elapsedMicroseconds;
315 | final times = _systemTimes[runtimeType]!;
316 | if (times.length >= _framesToMeasure) {
317 | times.removeFirst();
318 | }
319 | times.add(now);
320 | }
321 |
322 | void _storeTime(
323 | Map> measuredTimes,
324 | EntitySystem system,
325 | int afterSystem,
326 | int lastStop,
327 | ) {
328 | final times = measuredTimes[system.runtimeType]!;
329 | if (times.length >= _framesToMeasure) {
330 | times.removeFirst();
331 | }
332 | times.add(afterSystem - lastStop);
333 | }
334 |
335 | @override
336 | void addSystem(EntitySystem system) {
337 | super.addSystem(system);
338 | _systemTimes[system.runtimeType] = ListQueue(_framesToMeasure);
339 | _processEntityChangesTimes[system.runtimeType] =
340 | ListQueue(_framesToMeasure);
341 | }
342 |
343 | /// Returns the [PerformanceStats] for every system and and the
344 | /// [PerformanceStats] for changes to [int]s that require updates to other
345 | /// [EntitySystem]s and [Manager]s.
346 | List getPerformanceStats() {
347 | final result = [];
348 | _createPerformanceStats(_systemTimes, result);
349 | _createPerformanceStats(_processEntityChangesTimes, result);
350 | return result;
351 | }
352 |
353 | void _createPerformanceStats(
354 | Map> measuredTimes,
355 | List result,
356 | ) {
357 | for (final entry in measuredTimes.entries) {
358 | final measurements = entry.value.length;
359 | final sorted = List.from(entry.value)..sort();
360 | final meanTime = sorted[measurements ~/ 2];
361 | final averageTime =
362 | sorted.fold(0, (sum, item) => sum + item) / measurements;
363 | final minTime = sorted.first;
364 | final maxTime = sorted.last;
365 | result.add(
366 | PerformanceStats._internal(
367 | entry.key,
368 | measurements,
369 | minTime,
370 | maxTime,
371 | averageTime,
372 | meanTime,
373 | ),
374 | );
375 | }
376 | }
377 | }
378 |
379 | /// Performance statistics for all systems.
380 | @experimental
381 | class PerformanceStats {
382 | /// The [Type] of the system.
383 | Type system;
384 |
385 | /// The number of measurements.
386 | int measurements;
387 |
388 | /// The fastest ([minTime]) time in microseconds.
389 | int minTime;
390 |
391 | /// The slowest ([maxTime]) time in microseconds.
392 | int maxTime;
393 |
394 | /// The mean time in microseconds.
395 | int meanTime;
396 |
397 | /// The avaerage time in microseconds.
398 | double averageTime;
399 |
400 | PerformanceStats._internal(
401 | this.system,
402 | this.measurements,
403 | this.minTime,
404 | this.maxTime,
405 | this.averageTime,
406 | this.meanTime,
407 | );
408 |
409 | @override
410 | String toString() => '''
411 | PerformanceStats{system: $system, measurements: $measurements, minTime: $minTime, maxTime: $maxTime, meanTime: $meanTime, averageTime: $averageTime}''';
412 | }
413 |
--------------------------------------------------------------------------------
/test/dartemis/core/world_test.mocks.dart:
--------------------------------------------------------------------------------
1 | // Mocks generated by Mockito 5.4.5-wip from annotations
2 | // in dartemis/test/dartemis/core/world_test.dart.
3 | // Do not manually edit this file.
4 |
5 | // ignore_for_file: no_leading_underscores_for_library_prefixes
6 | import 'package:dartemis/dartemis.dart' as _i2;
7 | import 'package:mockito/mockito.dart' as _i1;
8 |
9 | // ignore_for_file: type=lint
10 | // ignore_for_file: avoid_redundant_argument_values
11 | // ignore_for_file: avoid_setters_without_getters
12 | // ignore_for_file: comment_references
13 | // ignore_for_file: deprecated_member_use
14 | // ignore_for_file: deprecated_member_use_from_same_package
15 | // ignore_for_file: implementation_imports
16 | // ignore_for_file: invalid_use_of_visible_for_testing_member
17 | // ignore_for_file: must_be_immutable
18 | // ignore_for_file: prefer_const_constructors
19 | // ignore_for_file: unnecessary_parenthesis
20 | // ignore_for_file: camel_case_types
21 | // ignore_for_file: subtype_of_sealed_class
22 |
23 | class _FakeWorld_0 extends _i1.SmartFake implements _i2.World {
24 | _FakeWorld_0(
25 | Object parent,
26 | Invocation parentInvocation,
27 | ) : super(
28 | parent,
29 | parentInvocation,
30 | );
31 | }
32 |
33 | /// A class which mocks [EntitySystem].
34 | ///
35 | /// See the documentation for Mockito's code generation for more information.
36 | class MockEntitySystem2 extends _i1.Mock implements _i2.EntitySystem {
37 | @override
38 | bool get passive => (super.noSuchMethod(
39 | Invocation.getter(#passive),
40 | returnValue: false,
41 | returnValueForMissingStub: false,
42 | ) as bool);
43 |
44 | @override
45 | set passive(bool? _passive) => super.noSuchMethod(
46 | Invocation.setter(
47 | #passive,
48 | _passive,
49 | ),
50 | returnValueForMissingStub: null,
51 | );
52 |
53 | @override
54 | int get group => (super.noSuchMethod(
55 | Invocation.getter(#group),
56 | returnValue: 0,
57 | returnValueForMissingStub: 0,
58 | ) as int);
59 |
60 | @override
61 | _i2.World get world => (super.noSuchMethod(
62 | Invocation.getter(#world),
63 | returnValue: _FakeWorld_0(
64 | this,
65 | Invocation.getter(#world),
66 | ),
67 | returnValueForMissingStub: _FakeWorld_0(
68 | this,
69 | Invocation.getter(#world),
70 | ),
71 | ) as _i2.World);
72 |
73 | @override
74 | int get frame => (super.noSuchMethod(
75 | Invocation.getter(#frame),
76 | returnValue: 0,
77 | returnValueForMissingStub: 0,
78 | ) as int);
79 |
80 | @override
81 | double get time => (super.noSuchMethod(
82 | Invocation.getter(#time),
83 | returnValue: 0.0,
84 | returnValueForMissingStub: 0.0,
85 | ) as double);
86 |
87 | @override
88 | double get delta => (super.noSuchMethod(
89 | Invocation.getter(#delta),
90 | returnValue: 0.0,
91 | returnValueForMissingStub: 0.0,
92 | ) as double);
93 |
94 | @override
95 | void begin() => super.noSuchMethod(
96 | Invocation.method(
97 | #begin,
98 | [],
99 | ),
100 | returnValueForMissingStub: null,
101 | );
102 |
103 | @override
104 | void process() => super.noSuchMethod(
105 | Invocation.method(
106 | #process,
107 | [],
108 | ),
109 | returnValueForMissingStub: null,
110 | );
111 |
112 | @override
113 | void end() => super.noSuchMethod(
114 | Invocation.method(
115 | #end,
116 | [],
117 | ),
118 | returnValueForMissingStub: null,
119 | );
120 |
121 | @override
122 | void processEntities(Iterable<_i2.Entity>? entities) => super.noSuchMethod(
123 | Invocation.method(
124 | #processEntities,
125 | [entities],
126 | ),
127 | returnValueForMissingStub: null,
128 | );
129 |
130 | @override
131 | bool checkProcessing() => (super.noSuchMethod(
132 | Invocation.method(
133 | #checkProcessing,
134 | [],
135 | ),
136 | returnValue: false,
137 | returnValueForMissingStub: false,
138 | ) as bool);
139 |
140 | @override
141 | void initialize(_i2.World? world) => super.noSuchMethod(
142 | Invocation.method(
143 | #initialize,
144 | [world],
145 | ),
146 | returnValueForMissingStub: null,
147 | );
148 |
149 | @override
150 | void destroy() => super.noSuchMethod(
151 | Invocation.method(
152 | #destroy,
153 | [],
154 | ),
155 | returnValueForMissingStub: null,
156 | );
157 |
158 | @override
159 | void addComponent(
160 | _i2.Entity? entity,
161 | T? component,
162 | ) =>
163 | super.noSuchMethod(
164 | Invocation.method(
165 | #addComponent,
166 | [
167 | entity,
168 | component,
169 | ],
170 | ),
171 | returnValueForMissingStub: null,
172 | );
173 |
174 | @override
175 | void removeComponent(_i2.Entity? entity) =>
176 | super.noSuchMethod(
177 | Invocation.method(
178 | #removeComponent,
179 | [entity],
180 | ),
181 | returnValueForMissingStub: null,
182 | );
183 |
184 | @override
185 | void deleteFromWorld(_i2.Entity? entity) => super.noSuchMethod(
186 | Invocation.method(
187 | #deleteFromWorld,
188 | [entity],
189 | ),
190 | returnValueForMissingStub: null,
191 | );
192 | }
193 |
194 | /// A class which mocks [EntitySystem].
195 | ///
196 | /// See the documentation for Mockito's code generation for more information.
197 | class MockEntitySystem extends _i1.Mock implements _i2.EntitySystem {
198 | @override
199 | bool get passive => (super.noSuchMethod(
200 | Invocation.getter(#passive),
201 | returnValue: false,
202 | returnValueForMissingStub: false,
203 | ) as bool);
204 |
205 | @override
206 | set passive(bool? _passive) => super.noSuchMethod(
207 | Invocation.setter(
208 | #passive,
209 | _passive,
210 | ),
211 | returnValueForMissingStub: null,
212 | );
213 |
214 | @override
215 | int get group => (super.noSuchMethod(
216 | Invocation.getter(#group),
217 | returnValue: 0,
218 | returnValueForMissingStub: 0,
219 | ) as int);
220 |
221 | @override
222 | _i2.World get world => (super.noSuchMethod(
223 | Invocation.getter(#world),
224 | returnValue: _FakeWorld_0(
225 | this,
226 | Invocation.getter(#world),
227 | ),
228 | returnValueForMissingStub: _FakeWorld_0(
229 | this,
230 | Invocation.getter(#world),
231 | ),
232 | ) as _i2.World);
233 |
234 | @override
235 | int get frame => (super.noSuchMethod(
236 | Invocation.getter(#frame),
237 | returnValue: 0,
238 | returnValueForMissingStub: 0,
239 | ) as int);
240 |
241 | @override
242 | double get time => (super.noSuchMethod(
243 | Invocation.getter(#time),
244 | returnValue: 0.0,
245 | returnValueForMissingStub: 0.0,
246 | ) as double);
247 |
248 | @override
249 | double get delta => (super.noSuchMethod(
250 | Invocation.getter(#delta),
251 | returnValue: 0.0,
252 | returnValueForMissingStub: 0.0,
253 | ) as double);
254 |
255 | @override
256 | void begin() => super.noSuchMethod(
257 | Invocation.method(
258 | #begin,
259 | [],
260 | ),
261 | returnValueForMissingStub: null,
262 | );
263 |
264 | @override
265 | void process() => super.noSuchMethod(
266 | Invocation.method(
267 | #process,
268 | [],
269 | ),
270 | returnValueForMissingStub: null,
271 | );
272 |
273 | @override
274 | void end() => super.noSuchMethod(
275 | Invocation.method(
276 | #end,
277 | [],
278 | ),
279 | returnValueForMissingStub: null,
280 | );
281 |
282 | @override
283 | void processEntities(Iterable<_i2.Entity>? entities) => super.noSuchMethod(
284 | Invocation.method(
285 | #processEntities,
286 | [entities],
287 | ),
288 | returnValueForMissingStub: null,
289 | );
290 |
291 | @override
292 | bool checkProcessing() => (super.noSuchMethod(
293 | Invocation.method(
294 | #checkProcessing,
295 | [],
296 | ),
297 | returnValue: false,
298 | returnValueForMissingStub: false,
299 | ) as bool);
300 |
301 | @override
302 | void initialize(_i2.World? world) => super.noSuchMethod(
303 | Invocation.method(
304 | #initialize,
305 | [world],
306 | ),
307 | returnValueForMissingStub: null,
308 | );
309 |
310 | @override
311 | void destroy() => super.noSuchMethod(
312 | Invocation.method(
313 | #destroy,
314 | [],
315 | ),
316 | returnValueForMissingStub: null,
317 | );
318 |
319 | @override
320 | void addComponent(
321 | _i2.Entity? entity,
322 | T? component,
323 | ) =>
324 | super.noSuchMethod(
325 | Invocation.method(
326 | #addComponent,
327 | [
328 | entity,
329 | component,
330 | ],
331 | ),
332 | returnValueForMissingStub: null,
333 | );
334 |
335 | @override
336 | void removeComponent(_i2.Entity? entity) =>
337 | super.noSuchMethod(
338 | Invocation.method(
339 | #removeComponent,
340 | [entity],
341 | ),
342 | returnValueForMissingStub: null,
343 | );
344 |
345 | @override
346 | void deleteFromWorld(_i2.Entity? entity) => super.noSuchMethod(
347 | Invocation.method(
348 | #deleteFromWorld,
349 | [entity],
350 | ),
351 | returnValueForMissingStub: null,
352 | );
353 | }
354 |
355 | /// A class which mocks [ComponentManager].
356 | ///
357 | /// See the documentation for Mockito's code generation for more information.
358 | class MockComponentManager extends _i1.Mock implements _i2.ComponentManager {
359 | @override
360 | _i2.World get world => (super.noSuchMethod(
361 | Invocation.getter(#world),
362 | returnValue: _FakeWorld_0(
363 | this,
364 | Invocation.getter(#world),
365 | ),
366 | returnValueForMissingStub: _FakeWorld_0(
367 | this,
368 | Invocation.getter(#world),
369 | ),
370 | ) as _i2.World);
371 |
372 | @override
373 | void registerSystem(_i2.EntitySystem? system) => super.noSuchMethod(
374 | Invocation.method(
375 | #registerSystem,
376 | [system],
377 | ),
378 | returnValueForMissingStub: null,
379 | );
380 |
381 | @override
382 | void removeComponentsOfEntity(_i2.Entity? entity) => super.noSuchMethod(
383 | Invocation.method(
384 | #removeComponentsOfEntity,
385 | [entity],
386 | ),
387 | returnValueForMissingStub: null,
388 | );
389 |
390 | @override
391 | List getComponentsByType() => (super.noSuchMethod(
392 | Invocation.method(
393 | #getComponentsByType,
394 | [],
395 | ),
396 | returnValue: