Line data Source code
1 : import 'package:ordered_set/comparing.dart'; 2 : import 'package:ordered_set/queryable_ordered_set.dart'; 3 : 4 : import '../../components.dart'; 5 : import '../game/base_game.dart'; 6 : 7 : /// This is a simple wrapper over [QueryableOrderedSet] to be used by 8 : /// [BaseGame] and [BaseComponent]. 9 : /// 10 : /// Instead of immediately modifying the component list, all insertion 11 : /// and removal operations are queued to be performed on the next tick. 12 : /// 13 : /// This will avoid any concurrent modification exceptions while the game 14 : /// iterates through the component list. 15 : /// 16 : /// This wrapper also guaranteed that prepare, onLoad, onMount and all the 17 : /// lifecycle methods are called properly. 18 : class ComponentSet extends QueryableOrderedSet<Component> { 19 : /// Components to be added on the next update. 20 : /// 21 : /// The component list is only changed at the start of each update to avoid 22 : /// concurrency issues. 23 : final List<Component> _addLater = []; 24 : 25 : /// Components to be removed on the next update. 26 : /// 27 : /// The component list is only changed at the start of each update to avoid 28 : /// concurrency issues. 29 : final Set<Component> _removeLater = {}; 30 : 31 : /// This is the "prepare" function that will be called *before* the 32 : /// component is added to the component list by the add/addAll methods. 33 : final void Function(Component child, {BaseGame? gameRef}) prepare; 34 : 35 25 : ComponentSet( 36 : int Function(Component e1, Component e2)? compare, 37 : this.prepare, 38 25 : ) : super(compare); 39 : 40 : /// Prepares and registers one component to be added on the next game tick. 41 : /// 42 : /// This is the interface compliant version; if you want to provide an 43 : /// explicit gameRef or await for the onLoad, use [addChild]. 44 : /// 45 : /// Note: the component is only added on the next tick. This method always 46 : /// returns true. 47 0 : @override 48 : bool add(Component c) { 49 0 : addChild(c); 50 : return true; 51 : } 52 : 53 : /// Prepares and registers a list of components to be added on the next game 54 : /// tick. 55 : /// 56 : /// This is the interface compliant version; if you want to provide an 57 : /// explicit gameRef or await for the onLoad, use [addChild]. 58 : /// 59 : /// Note: the components are only added on the next tick. This method always 60 : /// returns the total lenght of the provided list. 61 0 : @override 62 : int addAll(Iterable<Component> components) { 63 0 : addChildren(components); 64 0 : return components.length; 65 : } 66 : 67 : /// Prepares and registers one component to be added on the next game tick. 68 : /// 69 : /// This allows you to provide a specific gameRef if this component is being 70 : /// added from within another component that is already on a BaseGame. 71 : /// You can await for the onLoad function, if present. 72 : /// This method can be considered sync for all intents and purposes if no 73 : /// onLoad is provided by the component. 74 24 : Future<void> addChild(Component c, {BaseGame? gameRef}) async { 75 48 : prepare(c, gameRef: gameRef); 76 : 77 24 : final loadFuture = c.onLoad(); 78 : if (loadFuture != null) { 79 3 : await loadFuture; 80 : } 81 : 82 48 : _addLater.add(c); 83 : } 84 : 85 : /// Prepares and registers a list of component to be added on the next game 86 : /// tick. 87 : /// 88 : /// See [addChild] for more details. 89 5 : Future<void> addChildren( 90 : Iterable<Component> components, { 91 : BaseGame? gameRef, 92 : }) async { 93 15 : final ps = components.map((c) => addChild(c, gameRef: gameRef)); 94 10 : await Future.wait(ps); 95 : } 96 : 97 : /// Marks a component to be removed from the components list on the next game 98 : /// tick. 99 2 : @override 100 : bool remove(Component c) { 101 4 : _removeLater.add(c); 102 : return true; 103 : } 104 : 105 : /// Marks a list of components to be removed from the components list on the 106 : /// next game tick. 107 0 : void removeAll(Iterable<Component> components) { 108 0 : _removeLater.addAll(components); 109 : } 110 : 111 : /// Marks all existing components to be removed from the components list on 112 : /// the next game tick. 113 1 : @override 114 : void clear() { 115 2 : _removeLater.addAll(this); 116 : } 117 : 118 : /// Materializes the component list in reversed order. 119 4 : Iterable<Component> reversed() { 120 8 : return toList().reversed; 121 : } 122 : 123 : /// Call this on your update method. 124 : /// 125 : /// This method effectuates any pending operations of insertion or removal, 126 : /// and thus actually modifies the components set. 127 : /// Note: do not call this while iterating the set. 128 21 : void updateComponentList() { 129 93 : _removeLater.addAll(where((c) => c.shouldRemove)); 130 47 : _removeLater.forEach((c) { 131 5 : c.onRemove(); 132 5 : super.remove(c); 133 : }); 134 42 : _removeLater.clear(); 135 : 136 42 : if (_addLater.isNotEmpty) { 137 40 : final addNow = _addLater.toList(growable: false); 138 40 : _addLater.clear(); 139 40 : addNow.forEach((c) { 140 20 : super.add(c); 141 20 : c.onMount(); 142 : }); 143 : } 144 : } 145 : 146 1 : @override 147 : void rebalanceAll() { 148 1 : final elements = toList(); 149 : // bypass the wrapper because the components are already added 150 1 : super.clear(); 151 1 : elements.forEach(super.add); 152 : } 153 : 154 0 : @override 155 : void rebalanceWhere(bool Function(Component element) test) { 156 : // bypass the wrapper because the components are already added 157 0 : final elements = super.removeWhere(test).toList(); 158 0 : elements.forEach(super.add); 159 : } 160 : 161 : /// Creates a [ComponentSet] with a default value for the compare function, 162 : /// using the Component's priority for sorting. 163 : /// 164 : /// You must still provide your [prepare] function depending on the context. 165 25 : static ComponentSet createDefault( 166 : void Function(Component child, {BaseGame? gameRef}) prepare, 167 : ) { 168 25 : return ComponentSet( 169 65 : Comparing.on<Component>((c) => c.priority), 170 : prepare, 171 : ); 172 : } 173 : }