Line data Source code
1 : import 'package:equatable/equatable.dart'; 2 : 3 : import 'component.dart'; 4 : import 'extensions/extensions.dart'; 5 : import 'system.dart'; 6 : 7 : /// A container that holds components essentially. 8 : class Entity with EquatableMixin { 9 1 : Entity(this.guid, this.system); 10 : 11 : final String guid; 12 : 13 : /// The system where this entity is contained. 14 : final EntitySystem system; 15 : 16 : // Holding all components map through their type. 17 : final components = <Type, Component>{}.bs; 18 : 19 : /// Used internally for verifying Entity has not been destroyed before 20 : /// mutating any values. 21 : final isDestroyed = false.bs; 22 : 23 : /// Returns a matching component from type T. 24 1 : T get<T extends Component>() { 25 3 : var component = components.value[T]; 26 : return component as T; 27 : } 28 : 29 : /// Adds component to the entity. 30 1 : Entity operator +(Component component) { 31 2 : assert(!isDestroyed.value, 'Tried adding component to destroyed entity: ${toJson()}'); 32 3 : final copy = Map<Type, Component>.from(components.value); 33 : // Create new list and then add. 34 3 : copy[component.runtimeType] = component..ref = this; 35 2 : components.add(copy); 36 : // Call onAdded. 37 1 : component.onAdded(); 38 : return this; 39 : } 40 : 41 : /// For cascade 42 0 : void set(Component component) { 43 0 : var _ = this + component; 44 : } 45 : 46 : /// Removes component from the entity. 47 0 : Entity operator -(Type t) { 48 0 : assert(isDestroyed.value, 'Tried removing component from destroyed entity: ${toJson()}'); 49 0 : var component = components.value[t]; 50 : if (component != null) { 51 : // Call onRemoved. 52 0 : component.onRemoved(); 53 0 : final copy = Map<Type, Component>.from(components.value); 54 : // Created copy and removed this component. 55 0 : copy.remove(t); 56 : // Set value of the subject. 57 0 : components.add(copy); 58 : } 59 : 60 : return this; 61 : } 62 : 63 : /// For cascade 64 0 : void remove<T extends Component>() { 65 0 : var _ = this - T; 66 : } 67 : 68 : /// If entity has a component of type T 69 0 : bool has<T extends Component>() { 70 0 : return components.value.containsKey(T); 71 : } 72 : 73 : /// Checks if entity has a component of the type of the given object. 74 1 : bool hasComponent(Type type) { 75 3 : return components.value.containsKey(type); 76 : } 77 : 78 : /// Destroy an entity which will lead to following steps: 79 : /// 1. Remove all components 80 : /// 2. Remove from the system 81 : /// 3. Set `isDestroyed` to `true` 82 0 : void destroy() { 83 0 : for (var comp in components.value.values.toList()) { 84 0 : var _ = this - comp.runtimeType; 85 : } 86 0 : system.destroyed(this); 87 : // Add to sink that we are destroyed. 88 0 : isDestroyed.add(true); 89 0 : isDestroyed.close(); 90 : } 91 : 92 : /// Compares the component list between entities and returns a list of 93 : /// types of components that "a" has that "b" does not. 94 : /// 95 : /// E.g. 96 : /// 97 : /// final compared = a.componentDiff(b); 98 1 : Set<Type> componentDiff(Entity e) { 99 4 : final ecomps = e.components.value.keys.toSet(); 100 5 : return components.value.keys.toSet().difference(ecomps); 101 : } 102 : 103 0 : Map<String, dynamic> toJson() { 104 0 : return system.entityToJson(this); 105 : } 106 : 107 0 : static Entity fromJson(Map<String, dynamic> json, EntitySystem system) { 108 0 : return system.createFromJson(json); 109 : } 110 : 111 1 : @override 112 3 : List<Object> get props => [guid, components]; 113 : } 114 : 115 : /// A class that describe a way to filter entities. 116 : /// 117 : /// There are 3 properties, each with it's own functionality. 118 : /// all: The entity MUST contain all of these components. 119 : /// any: The entity MUST contain at *least* one of these components. 120 : /// inverse: Flips the logic, so MUST becomes MUST NOT. 121 : /// 122 : /// var matcher = EntityMatcher(all: [CountComponent, PriceComponent]) 123 : /// This matcher would match any entity that has both the CountComponent AND the 124 : /// Price Component. 125 : /// 126 : /// var matcher = EntityMatcher(all: [CartComponent], any: [PriceComponent, CouponComponent]) 127 : /// This matcher would match any entity that has the CartComponent, and has at least one between 128 : /// the PriceComponent and CouponComponent. 129 : /// 130 : /// var matcher = EntityMatcher(all: [DiscontinuedComponent, PriceComponent], reverse: true) 131 : /// This matcher would match any entity that DOES NOT have the DiscontinuedComponent and PriceComponent. 132 : /// 133 : /// var matcher = EntityMatcher(any: [DiscontinuedComponent, OutOfStockComponent, DisabledComponent], reverse: true) 134 : /// This matcher would match any entity that DOES NOT have any one of these components. 135 : class EntityMatcher extends Equatable { 136 1 : const EntityMatcher({ 137 : this.all = const {}, 138 : this.any = const {}, 139 : this.reverse = false, 140 : }); 141 : 142 : final Set<Type> all; 143 : final Set<Type> any; 144 : final bool reverse; 145 : 146 0 : bool contains(Type type) { 147 0 : return all.contains(type) || any.contains(type); 148 : } 149 : 150 1 : bool matches(Entity entity) { 151 4 : if (any.isEmpty && all.isEmpty) { 152 : return true; 153 : } 154 : 155 1 : final anyMatched = matchesAny(entity); 156 1 : final allMatched = matchesAll(entity); 157 : 158 : return anyMatched && allMatched; 159 : } 160 : 161 1 : bool matchesAll(Entity entity) { 162 2 : if (all.isEmpty) return true; 163 : // If reverse, we want to make sure we contain NONE of the components 164 : // in all. 165 1 : if (reverse) { 166 2 : for (final t in all) { 167 1 : if (entity.hasComponent(t)) { 168 : return false; 169 : } 170 : } 171 : } else { 172 2 : for (final t in all) { 173 1 : if (!entity.hasComponent(t)) { 174 : return false; 175 : } 176 : } 177 : } 178 : 179 : return true; 180 : } 181 : 182 1 : bool matchesAny(Entity entity) { 183 2 : if (any.isEmpty) return true; 184 : // If reversed, we match if it doesn't contain any of the any components 185 1 : if (reverse) { 186 2 : for (var t in any) { 187 2 : if (entity.hasComponent(t) == false) { 188 : return true; 189 : } 190 : } 191 : } else { 192 2 : for (var t in any) { 193 1 : if (entity.hasComponent(t)) { 194 : return true; 195 : } 196 : } 197 : } 198 : 199 : return false; 200 : } 201 : 202 0 : @override 203 0 : List<Object?> get props => [all, any, reverse]; 204 : }