Line data Source code
1 : import 'dart:async'; 2 : 3 : import 'package:uuid/uuid.dart'; 4 : 5 : import 'component.dart'; 6 : import 'entity.dart'; 7 : import 'extensions/extensions.dart'; 8 : import 'mixins.dart'; 9 : 10 : // A function that takes JSON data and returns a component. 11 : typedef ComponentDeserializerFunction = Component? Function(Map<String, dynamic> data); 12 : 13 : // A function that takes a component and returns JSON-esque data. 14 : typedef ComponentSerializerFunction = Map<String, dynamic> Function(SerializableComponent component); 15 : 16 : // A function that takes in an entity and returns JSON-esque data. 17 : typedef EntityToJsonFunction = Map<String, dynamic> Function(Entity entity); 18 : 19 : // A function that takes in an entity and returns T which would be a value 20 : // that can be used to determine if an entity is unique. 21 : typedef EntityUniqueKeyGeneratorFunction<T> = T Function(Entity entity); 22 : 23 : // A function that takes in JSON and returns an Entity; 24 : typedef EntityFromJsonFunction = Entity Function( 25 : Map<String, dynamic> json, 26 : EntitySystem system, 27 : ); 28 : 29 : /// This system is for keeping track of all created entities. 30 : class EntitySystem<T> { 31 : final uuid = const Uuid(); 32 : 33 : // The listener that updates the public list of entities. 34 : late StreamSubscription _listener; 35 : 36 : EntityUniqueKeyGeneratorFunction<T>? uniqueKeyGeneratorFunction; 37 : 38 1 : EntitySystem() { 39 : // When entities updates, map over and update the public entity list. 40 5 : _listener = _entities.stream.listen((ents) { 41 2 : final list = ents.values.toList(); 42 2 : entities.add(list); 43 : }); 44 : } 45 : 46 : // Generate a system with a custom unique key generator function. 47 0 : static EntitySystem<T> withUniqueKeyGenerator<T>( 48 : EntityUniqueKeyGeneratorFunction<T> uniqueKeyGeneratorFunction, 49 : ) { 50 0 : final system = EntitySystem<T>(); 51 0 : system.uniqueKeyGeneratorFunction = uniqueKeyGeneratorFunction; 52 : return system; 53 : } 54 : 55 : /// A list of registered deserializer functions. 56 : List<ComponentDeserializerFunction> deserializers = [ 57 : componentFromJson, 58 : ]; 59 : 60 : /// A list of registered serializer functions. 61 : List<ComponentSerializerFunction> serializers = [ 62 : componentToJson, 63 : ]; 64 : 65 : /// The function called to turn an entity into JSON. 66 : EntityToJsonFunction entityToJsonFunction = defaultEntityToJsonFunction; 67 : 68 : /// The function called to turn JSON into an entity; 69 : EntityFromJsonFunction entityFromJsonFunction = defaultEntityFromJsonFunction; 70 : 71 : /// The internal registry of all created entities. 72 : final _entities = <T, Entity>{}.bs; 73 : 74 : /// An observable list of entities. 75 : final entities = <Entity>[].bs; 76 : 77 : /// Call this to release resources and workers. 78 0 : void dispose() { 79 : /// Clear entities and cancel subscription. 80 0 : _listener.cancel(); 81 0 : _entities.close(); 82 0 : entities.close(); 83 : } 84 : 85 : /// Remove all entities. 86 0 : void flush() { 87 0 : if (_entities.hasValue) { 88 0 : _entities.value.forEach((guid, e) => e.destroy()); 89 0 : _entities.value.clear(); 90 : } 91 : } 92 : 93 : /// Register a deserializer function. 94 0 : void registerDeserializer(ComponentDeserializerFunction deserializer) { 95 0 : if (!deserializers.contains(deserializer)) { 96 0 : deserializers.add(deserializer); 97 : } 98 : } 99 : 100 : /// Register a serializer function. 101 0 : void registerSerializer(ComponentSerializerFunction serializer) { 102 0 : if (!serializers.contains(serializer)) { 103 0 : serializers.add(serializer); 104 : } 105 : } 106 : 107 : /// The way a new entity should be created. 108 1 : Entity create() { 109 3 : final entity = Entity(uuid.v4(), this); 110 1 : return setEntity(entity); 111 : } 112 : 113 : /// Deserializing entities from JSON. 114 0 : Entity createFromJson(Map<String, dynamic> json) { 115 0 : final entity = entityFromJsonFunction(json, this); 116 0 : return setEntity(entity); 117 : } 118 : 119 : /// This function actually sets the entity in the list. 120 : /// Left public so that if you want to use your own method for creating entities 121 : /// you can, as long as you call this method so the system tracks the entity. 122 1 : Entity setEntity(Entity entity) { 123 2 : final current = _entities.value; 124 : // If we have a custom key generator use that. 125 : late final T key; 126 1 : if (uniqueKeyGeneratorFunction != null) { 127 0 : key = uniqueKeyGeneratorFunction!(entity); 128 : } else { 129 1 : key = entity.guid as T; 130 : } 131 7 : final newList = Map.fromIterables([...current.keys, key], [...current.values, entity]); 132 2 : _entities.add(newList); 133 : // Return the entity. 134 4 : return _entities.value[entity.guid]!; 135 : } 136 : 137 : /// Standard serializer that turns an entity into JSON. 138 0 : Map<String, dynamic> entityToJson(Entity entity) { 139 0 : return entityToJsonFunction(entity); 140 : } 141 : 142 : /// When an entity has been destroyed, we remove it from the list 143 : /// and that will automatically sync that to the observable list. 144 0 : void destroyed(Entity entity) { 145 0 : final current = _entities.value; 146 : // Drop the guid and remove the entity from the list. 147 0 : final newList = Map.fromIterables([...current.keys], [...current.values].where((e) => e != entity)); 148 : // Set the new list. 149 0 : _entities.add(newList); 150 : } 151 : } 152 : 153 : /// Standard serializer that turns an entity into JSON. 154 0 : Map<String, dynamic> defaultEntityToJsonFunction(Entity entity) { 155 0 : List<Map<String, dynamic>> componentData = []; 156 0 : entity.components.value.forEach( 157 0 : (type, component) { 158 0 : if (component is SerializableComponent) { 159 0 : componentData.add({'type': type.toString(), 'data': (component as SerializableComponent).toJson()}); 160 : } 161 : }, 162 : ); 163 0 : return <String, dynamic>{ 164 : 'components': componentData, 165 : }; 166 : } 167 : 168 0 : Entity defaultEntityFromJsonFunction(Map<String, dynamic> json, EntitySystem system) { 169 0 : var e = Entity(json['guid'], system); 170 0 : if (json['components'] != null) { 171 0 : final comps = json['components'] as List<dynamic>; 172 0 : for (var compData in comps) { 173 : final data = compData as Map<String, dynamic>; 174 0 : Component? c = componentFromJson(data); 175 : // if not contained in default system, check serializers. 176 : if (c == null) { 177 0 : for (var serializer in system.deserializers) { 178 0 : c = serializer.call(data); 179 : } 180 : } 181 : 182 : /// If we actually got a component, add it. 183 : if (c != null) { 184 0 : e += c; 185 : } 186 : } 187 : } 188 : 189 : return e; 190 : } 191 : 192 : /// A map of strings to types. 193 0 : Component? componentFromJson(Map<String, dynamic> json) { 194 : return null; 195 : } 196 : 197 0 : Map<String, dynamic> componentToJson(SerializableComponent component) { 198 0 : return component.toJson(); 199 : }