├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── SponsoredByMyTextAi.png
├── main.dart
└── pubspec.yaml
├── lib
├── src
│ ├── cache.dart
│ ├── weak_container.dart
│ └── weak_map.dart
└── weak_map.dart
├── pubspec.yaml
└── test
├── cache_test.dart
├── weak_container_test.dart
└── weak_map_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 | .idea_modules/
18 | out/
19 | .idea/**/dataSources/
20 | .idea/**/dataSources.ids
21 | .idea/**/dataSources.local.xml
22 | .idea/**/sqlDataSources.xml
23 | .idea/**/dynamic.xml
24 | .idea/**/uiDesigner.xml
25 | .idea/**/dbnavigator.xml
26 | .idea/**/contentModel.xml
27 | .idea/**/workspace.xml
28 | .idea/**/tasks.xml
29 | .idea/**/usage.statistics.xml
30 | .idea/**/dictionaries
31 | .idea/**/shelf
32 | **/.idea/workspace.xml
33 | .idea/**/gradle.xml
34 | .idea/**/libraries/
35 | .idea/**/modules.xml
36 |
37 | # The .vscode folder contains launch configuration and tasks you configure in
38 | # VS Code which you may wish to be included in version control, so this line
39 | # is commented out by default.
40 | #.vscode/
41 |
42 | # Flutter/Dart/Pub related
43 | **/doc/api/
44 | .dart_tool/
45 | .flutter-plugins
46 | .flutter-plugins-dependencies
47 | .packages
48 | .pub-cache/
49 | .pub/
50 | build/
51 | **/build/
52 | pubspec.lock
53 | **/pubspec.lock
54 |
55 | # Android related
56 | **/android/**/gradle-wrapper.jar
57 | **/android/.gradle
58 | **/android/captures/
59 | **/android/gradlew
60 | **/android/gradlew.bat
61 | **/android/local.properties
62 | **/android/**/GeneratedPluginRegistrant.java
63 |
64 | # iOS/XCode related
65 | **/ios/**/*.mode1v3
66 | **/ios/**/*.mode2v3
67 | **/ios/**/*.moved-aside
68 | **/ios/**/*.pbxuser
69 | **/ios/**/*.perspectivev3
70 | **/ios/**/*sync/
71 | **/ios/**/.sconsign.dblite
72 | **/ios/**/.tags*
73 | **/ios/**/.vagrant/
74 | **/ios/**/DerivedData/
75 | **/ios/**/Icon?
76 | **/ios/**/Pods/
77 | **/ios/**/.symlinks/
78 | **/ios/**/profile
79 | **/ios/**/xcuserdata
80 | **/ios/.generated/
81 | **/ios/Flutter/App.framework
82 | **/ios/Flutter/Flutter.framework
83 | **/ios/Flutter/Flutter.podspec
84 | **/ios/Flutter/Generated.xcconfig
85 | **/ios/Flutter/app.flx
86 | **/ios/Flutter/app.zip
87 | **/ios/Flutter/flutter_assets/
88 | **/ios/Flutter/flutter_export_environment.sh
89 | **/ios/ServiceDefinitions.json
90 | **/ios/Runner/GeneratedPluginRegistrant.*
91 |
92 | # Exceptions to above rules.
93 | !**/ios/**/default.mode1v3
94 | !**/ios/**/default.mode2v3
95 | !**/ios/**/default.pbxuser
96 | !**/ios/**/default.perspectivev3
97 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
98 |
99 | # Arquivos de cache-serializado do Android studio 3.1+.
100 | .idea/caches/build_file_checksums.ser
101 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f7a6a7906be96d2288f5d63a5a54c515a6e987fe
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 4.0.1
2 |
3 | * Sponsored by [MyText.ai](https://mytext.ai)
4 |
5 | [](https://mytext.ai)
6 |
7 | ## 3.0.1
8 |
9 | * Flutter 3.10.0 and Dart 3.0.0
10 |
11 | ## 2.1.0
12 |
13 | * `cache2states_3params((state1, state2) => (param1, param2, param3) => ...);`
14 |
15 | ## 2.0.4
16 |
17 | * Docs improvement.
18 |
19 | ## 2.0.2
20 |
21 | * NNBD improvement.
22 |
23 | ## 2.0.0
24 |
25 | * Cache functions with extra parameters.
26 | * Rename of cache functions to make them easier to use.
27 |
28 | Now, cache functions are:
29 |
30 | ```
31 | cache1state((state) => () => ...);
32 | cache1state_1param((state) => (parameter) => ...);
33 | cache1state_2params((state) => (param1, param2) => ...);
34 | cache2states((state1, state2) => () => ...);
35 | cache2states_1param((state1, state2) => (parameter) => ...);
36 | cache2states_2params((state1, state2) => (param1, param2) => ...);
37 | cache3states((state1, state2, state3) => () => ...);
38 | cache1state_0params_x((state1, extra) => () => ...);
39 | cache2states_0params_x((state1, state2, extra) => () => ...);
40 | cache3states_0params_x((state1, state2, state3, extra) => () => ...);
41 | ```
42 |
43 | ## 1.4.0-nullsafety.0
44 |
45 | * Migrating to null safety
46 |
47 | ## 1.3.2
48 |
49 | * Type parameters rename (clean-code).
50 |
51 | ## 1.3.1
52 |
53 | * cache3_0.
54 |
55 | ## 1.2.2
56 |
57 | * Dependency bump.
58 |
59 | ## 1.2.1
60 |
61 | * Cache functions.
62 |
63 | ## 1.1.2
64 |
65 | * Add is by identity.
66 | * Docs improvement.
67 |
68 | ## 1.0.6
69 |
70 | * Removed dependency on Flutter (it's now pure Dart).
71 |
72 | ## 1.0.2
73 |
74 | * Example.
75 |
76 | ## 1.0.0
77 |
78 | * WeakMap and WeakContainer.
79 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 by Marcelo Glasberg
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted
4 | provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
7 | and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of
10 | conditions and the following disclaimer in the documentation and/or other materials provided
11 | with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
14 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
15 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
16 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
19 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://pub.dev/packages/weak_map)
2 | [](https://pub.dev/packages/weak_map)
3 | [](https://github.com/marcglasberg/weak_map)
4 | 
5 | 
6 | [](https://glasberg.dev/)
7 | [](https://pub.dev/publishers/glasberg.dev/packages)
8 | [](https://pub.dev/packages/weak_map)
9 |
10 | #### Sponsor
11 |
12 | [](https://mytext.ai)
13 |
14 | # weak_map
15 |
16 | This package contains the classes:
17 |
18 | * **WeakMap**
19 | * **WeakContainer**
20 |
21 | And also the functions:
22 |
23 | * **cache1state**
24 | * **cache1state_1param**
25 | * **cache1state_2params**
26 | * **cache2states**
27 | * **cache2states_1param**
28 | * **cache2states_2params**
29 | * **cache3states**
30 | * **cache1state_0params_x**
31 | * **cache2states_0params_x**
32 | * **cache3states_0params_x**
33 |
34 | ### Why is this package useful?
35 |
36 | 1. Dart doesn't allow for real
37 | weak-references, but this package allows you to go as close as possible
38 | (internally it uses the
39 | Expando class). The Dart engine stores a value in memory while it is reachable (and can
40 | potentially be used). Usually, keys in a map are considered reachable and kept in memory while
41 | the map itself is in memory. This means if we put an object into a map or into a variable, then
42 | while the map is alive, the object will be alive as well, even if there are no other references
43 | to it. It occupies memory and may not be garbage collected. WeakMap and WeakContainer are
44 | fundamentally different in this aspect. They don't prevent garbage-collection of key objects.
45 |
46 | 2. Caches that keep the result of expensive processes calculated over immutable data can also
47 | benefit from weak-maps. I here provide functions similar to the ones of the
48 | reselect package, but better. This can be used
49 | with Redux or with any other calculations over immutable data.
50 |
51 |
52 |
53 | ## WeakMap
54 |
55 | A WeakMap lets you garbage-collect its keys. Please note the **keys** can be garbage-collected, not
56 | their corresponding values.
57 |
58 | This means if you use some object as a key to a map-entry, this alone will not prevent Dart to
59 | garbage-collect this object.
60 |
61 | In other words, after all other references to that object have been destroyed, its entry
62 | (key and value) may be removed automatically from the map at any moment. To create a map:
63 |
64 | ```
65 | var map = WeakMap();
66 | ```
67 |
68 | To add and retrieve a value:
69 |
70 | ```
71 | map["John"] = 42;
72 | var age = map["John"];
73 | ```
74 |
75 | The following map methods work as expected:
76 |
77 | ```
78 | map.remove("John")
79 | map.clear()
80 | map.contains("John"))
81 | ```
82 |
83 | However, adding some `null` value to the map is the same as removing the key:
84 |
85 | ```
86 | map["John"] = null; // Same as map.remove("John")
87 | ```
88 |
89 | **Notes:**
90 |
91 | 1. The keys are compared using object **identity**, and not object equivalence (operator `==`).
92 |
93 | 2. If you use null, a number, a boolean, a String, or a const type as the map key, it will act like
94 | a regular map, because these types are never garbage-collected. All other types of object may be
95 | garbage-collected.
96 |
97 | 3. To retrieve a value added to the map, you can use the equivalent syntaxes `var y = map[x]`
98 | or `var y = map.get(x)`.
99 |
100 | 4. Doing `map[x] = y` is equivalent to `map.add(key: x, value: y)`, but the object is later
101 | retrieved by **identity**.
102 |
103 |
104 |
105 | ## WeakContainer
106 |
107 | As previously explained, Dart doesn't have real weak-references. But you can check that some object
108 | is the same you had before.
109 |
110 | To create a weak-container:
111 |
112 | ```
113 | var obj = Object();
114 | var ref = WeakContainer(obj);
115 | var someObj = Random().nextBool() ? obj : Object();
116 | print(ref.contains(someObj)); // True or false.
117 | ```
118 |
119 | This will print `true` if `someObj` is the same as the original `obj`, and will print `false` if
120 | it's a different object, compared by identity. If all references to the original `obj` have been
121 | destroyed, the weak-container will **not** prevent `obj` to be garbage-collected.
122 |
123 |
124 |
125 | ### Why doesn't Dart allow for real weak-references, anyway?
126 |
127 | Because the creators of Dart don't want the GC (garbage-collector) to be "visible".
128 |
129 | Expandos are not equivalent to weak-references (meaning the Java `WeakReference` behavior). A weak
130 | reference is one that doesn't keep the referenced object alive, so the weak reference value may
131 | change to `null` at any time in the program. This makes the GC visible in the program.
132 |
133 | Expandos are maps (from key to value) which won't keep the key alive. There is no way to distinguish
134 | an Expando that garbage collects the entry when the key dies, and one that doesn't, because you
135 | don't have the key to do the lookup anymore.
136 |
137 | Basically, it means that an expando keeps a value alive as long as you have a reference to both the
138 | expando and the key, and after that, you can't check if the entry is there or not. With expandos,
139 | the GC need not be part of the language specification. It's just an optimization that
140 | implementations (are expected to) do to release memory that isn't needed anymore. Disabling the GC
141 | will not change the behavior of programs unless they run out of memory.
142 |
143 |
144 |
145 | ## Cache
146 |
147 | Suppose you have some **immutable** information, which we call "state", and some parameters. We want
148 | to perform some expensive process (calculation, selection filtering etc) over the state, and we want
149 | to cache the result.
150 |
151 | For example, suppose you want to filter an **immutable list of millions of users**, to create a new
152 | list with only the names that start with some text. You could filter the users list to remove all
153 | other names, like this:
154 |
155 | ```
156 | List filter(String text) => users.where((user)=>user.name.startsWith(text)).toList();
157 | ```
158 |
159 | This is an expensive process, so you may want to cache the filtered list.
160 |
161 | In this example, we have a single state and a single parameter, so we're going to use
162 | the `cache1state_1param` method:
163 |
164 | ```
165 | static List filter(Users users, String text)
166 | => _filter(users)(text);
167 |
168 | static final _filter = cache1state_1param(
169 | (Users users)
170 | => (String text)
171 | => users.where((user)=>user.name.startsWith(text)).toList());
172 | ```
173 |
174 | The above code will calculate the filtered list only once, and then return it when the `filter`
175 | function is called again with the same `users` and `text`.
176 |
177 | If the function is called with a **different** `users` and/or `text`, it will recalculate and cache
178 | the new result.
179 |
180 | However, it treats the state and the parameter differently. If you call the function while keeping
181 | the **same state** and changing only the parameter, it will cache all the results, one for each
182 | parameter.
183 |
184 | However, as soon as you call the function with a **changed state**, it will delete all of its
185 | previous cached information, since it understands that they are no longer useful.
186 |
187 | And even if you don't call that function ever again, it will delete the cached information if it
188 | detects that the state is no longer used in other parts of the program. In other words, it keeps the
189 | cached information in a weak-map, so that the cache will not hold to old information and have a
190 | negative impact in memory usage.
191 |
192 | Some functions, marked with an "x", also let you pass some extra information which is not used in
193 | any way to decide whether the cache should be used/recalculated/evicted.
194 |
195 | For the moment, the following 10 methods are provided, which combine 1, 2 or 3 states with 0, 1 or 2
196 | parameters, and possibly some extra information:
197 |
198 | ```
199 | cache1state((state) => () => ...);
200 | cache1state_1param((state) => (parameter) => ...);
201 | cache1state_2params((state) => (parameter1, parameter2) => ...);
202 | cache2states((state1, state2) => () => ...);
203 | cache2states_1param((state1, state2) => (parameter) => ...);
204 | cache2states_2params((state1, state2) => (parameter1, parameter2) => ...);
205 | cache3states((state1, state2, state3) => () => ...);
206 | cache1state_0params_x((state1, extra) => () => ...);
207 | cache2states_0params_x((state1, state2, extra) => () => ...);
208 | cache3states_0params_x((state1, state2, state3, extra) => () => ...);
209 | ```
210 |
211 | I have created only those above, because for my own usage I never required more than that. Please,
212 | open an issue
213 | to ask for more variations in case you feel the need.
214 |
215 | **Note:** These cache functions are similar to the "createSelector" functions found in the
216 | reselect package. The differences are: First, here
217 | you can keep any number of cached results for each function, one for each time the function is
218 | called with the same state and different parameters. Meanwhile, the reselect package only keeps a
219 | single cached result per function. Second, here it discards the cached information when the state
220 | changes or is no longer used in other parts of the program. Meanwhile, the reselect package will
221 | always keep the states and cached results in memory.
222 |
223 |
224 |
225 | ***
226 |
227 | ## By Marcelo Glasberg
228 |
229 | _glasberg.dev_
230 |
231 | _github.com/marcglasberg_
232 |
233 | _linkedin.com/in/marcglasberg/_
234 |
235 | _twitter.com/glasbergmarcelo_
236 |
237 |
238 | _stackoverflow.com/users/3411681/marcg_
239 |
240 | _medium.com/@marcglasberg_
241 |
242 |
243 | *My article in the official Flutter documentation*:
244 |
245 | * Understanding
246 | constraints
247 |
248 | *The Flutter packages I've authored:*
249 |
250 | * async_redux
251 | * provider_for_redux
252 | * i18n_extension
253 | * align_positioned
254 | * network_to_file_image
255 | * image_pixels
256 | * matrix4_transform
257 | * back_button_interceptor
258 | * indexed_list_view
259 | * animated_size_and_fade
260 | * assorted_layout_widgets
261 | * weak_map
262 | * themed
263 | * bdd_framework
264 | *
265 | tiktoken_tokenizer_gpt4o_o1
266 |
267 | *My Medium Articles:*
268 |
269 | *
270 | Async Redux: Flutter’s non-boilerplate version of Redux
271 | (versions:
272 | Português)
273 | *
274 | i18n_extension
275 | (versions:
276 | Português)
277 | *
278 | Flutter: The Advanced Layout Rule Even Beginners Must Know
279 | (versions: русский)
280 | *
281 | The New Way to create Themes in your Flutter App
282 |
283 | [](https://mytext.ai)
284 |
--------------------------------------------------------------------------------
/example/SponsoredByMyTextAi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcglasberg/weak_map/7acc88d56b52cffb41cdc170850e216160d2b211/example/SponsoredByMyTextAi.png
--------------------------------------------------------------------------------
/example/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:weak_map/weak_map.dart';
2 |
3 | void main() {
4 | var map = WeakMap();
5 |
6 | // 1)
7 | print('\nmap["A"] = 1');
8 | map["A"] = 1;
9 | print('A = ${map["A"]}'); // A = 1
10 | print('A exists in map = ${map.contains("A")}'); // A = true
11 |
12 | // 2)
13 | print('\nDoes not set B');
14 | print('B = ${map["B"]}'); // B = null
15 | print('B exists in map = ${map.contains("B")}'); // A = false
16 |
17 | // 3)
18 | print('\nmap["A"] = null');
19 | map["A"] = null;
20 | print('A = ${map["A"]}'); // A = null
21 | // Adding some null value to the map is the same as removing the key.
22 | print('A exists in map = ${map.contains("A")}'); // A = false
23 |
24 | // 4)
25 | print('\nvalue = WeakContainer("X")');
26 | var value = WeakContainer("X");
27 | print('Contains X = ${value.contains("X")}'); // Contains X = true
28 | print('Contains Y = ${value.contains("Y")}'); // Contains Y = false
29 | }
30 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | publish_to: "none"
3 | version: 1.0.0
4 |
5 | environment:
6 | sdk: '>=2.19.0 <4.0.0'
7 |
8 | dependencies:
9 | weak_map:
10 | path: ../
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/src/cache.dart:
--------------------------------------------------------------------------------
1 | import 'dart:collection';
2 |
3 | import 'package:weak_map/weak_map.dart';
4 |
5 | typedef R1_0 = Result Function();
6 | typedef F1_0 = R1_0 Function(State1);
7 |
8 | /// Cache for 1 immutable state, and no parameters.
9 | F1_0 cache1state(F1_0 f) {
10 | WeakContainer? _s1;
11 | late WeakMap weakMap;
12 |
13 | return (State1 s1) {
14 | return () {
15 | if (_s1 == null || !_s1!.contains(s1)) {
16 | weakMap = WeakMap();
17 | _s1 = WeakContainer(s1);
18 | var result = f(s1)();
19 | weakMap[s1] = result;
20 | return result;
21 | }
22 | //
23 | else {
24 | return weakMap[s1] as Result;
25 | }
26 | };
27 | };
28 | }
29 |
30 | typedef R1_1 = Result Function(Param1);
31 | typedef F1_1 = R1_1 Function(State1);
32 |
33 | /// Cache for 1 immutable state, and 1 parameter.
34 | F1_1 cache1state_1param(
35 | F1_1 f,
36 | ) {
37 | WeakContainer? _s1;
38 | late WeakMap> weakMap;
39 |
40 | return (State1 s1) {
41 | return (Param1 p1) {
42 | if (_s1 == null || !_s1!.contains(s1)) {
43 | weakMap = WeakMap();
44 | Map map = HashMap();
45 | weakMap[s1] = map;
46 | _s1 = WeakContainer(s1);
47 | var result = f(s1)(p1);
48 | map[p1] = result;
49 | return result;
50 | }
51 | //
52 | else {
53 | Map map = weakMap[s1]!;
54 | if (!map.containsKey(p1)) {
55 | var result = f(s1)(p1);
56 | map[p1] = result;
57 | return result;
58 | }
59 | //
60 | else
61 | return map[p1] as Result;
62 | }
63 | };
64 | };
65 | }
66 |
67 | typedef R1_2 = //
68 | Result Function(Param1, Param2);
69 |
70 | typedef F1_2 = //
71 | R1_2 Function(State1);
72 |
73 | /// Cache for 1 immutable state, and 2 parameters.
74 | F1_2 cache1state_2params(
75 | F1_2 f) {
76 | WeakContainer? _s1;
77 | late WeakMap, Result>> weakMap;
78 |
79 | return (State1 s1) {
80 | return (Param1 p1, Param2 p2) {
81 | var parP = _Pair(p1, p2);
82 | if (_s1 == null || !_s1!.contains(s1)) {
83 | weakMap = WeakMap();
84 | Map<_Pair, Result> map = HashMap();
85 | weakMap[s1] = map;
86 | _s1 = WeakContainer(s1);
87 | var result = f(s1)(p1, p2);
88 | map[parP] = result;
89 | return result;
90 | }
91 | //
92 | else {
93 | Map<_Pair, Result> map = weakMap[s1]!;
94 | if (!map.containsKey(parP)) {
95 | var result = f(s1)(p1, p2);
96 | map[parP] = result;
97 | return result;
98 | }
99 | return map[parP] as Result;
100 | }
101 | };
102 | };
103 | }
104 |
105 | typedef R2_0 = Result Function();
106 | typedef F2_0 = R2_0 Function(State1, State2);
107 |
108 | /// Cache for 2 immutable states, and no parameters.
109 | F2_0 cache2states(
110 | F2_0 f,
111 | ) {
112 | WeakContainer? _s1, _s2;
113 | late WeakMap> weakMap1;
114 | WeakMap weakMap2;
115 |
116 | return (State1 s1, State2 s2) {
117 | return () {
118 | if (_s1 == null ||
119 | _s2 == null || //
120 | !_s1!.contains(s1) ||
121 | !_s2!.contains(s2)) {
122 | _s1 = WeakContainer(s1);
123 | _s2 = WeakContainer(s2);
124 |
125 | var result = f(s1, s2)();
126 | weakMap1 = WeakMap();
127 | weakMap2 = WeakMap();
128 | weakMap2[s2] = result;
129 | weakMap1[s1] = weakMap2;
130 | return result;
131 | }
132 | //
133 | else {
134 | return weakMap1[s1]![s2] as Result;
135 | }
136 | };
137 | };
138 | }
139 |
140 | typedef R2_1 = Result Function(Param1);
141 |
142 | typedef F2_1 = R2_1 Function(
143 | State1,
144 | State2,
145 | );
146 |
147 | /// Cache for 2 immutable states, and 1 parameter.
148 | F2_1 cache2states_1param(
149 | F2_1 f) {
150 | WeakContainer? _s1, _s2;
151 | late WeakMap>> weakMap1;
152 | WeakMap> weakMap2;
153 |
154 | return (State1 s1, State2 s2) {
155 | return (Param1 p1) {
156 | if (_s1 == null ||
157 | _s2 == null || //
158 | !_s1!.contains(s1) ||
159 | !_s2!.contains(s2)) {
160 | _s1 = WeakContainer(s1);
161 | _s2 = WeakContainer(s2);
162 |
163 | var result = f(s1, s2)(p1);
164 | weakMap1 = WeakMap();
165 | weakMap2 = WeakMap();
166 | Map map = HashMap();
167 | map[p1] = result;
168 | weakMap2[s2] = map;
169 | weakMap1[s1] = weakMap2;
170 | return result;
171 | }
172 | //
173 | else {
174 | Map map = weakMap1[s1]![s2]!;
175 | if (!map.containsKey(p1)) {
176 | var result = f(s1, s2)(p1);
177 | map[p1] = result;
178 | return result;
179 | }
180 |
181 | return map[p1] as Result;
182 | }
183 | };
184 | };
185 | }
186 |
187 | typedef R2_2 = //
188 | Result Function(Param1, Param2);
189 |
190 | typedef F2_2 = //
191 | R2_2 Function(State1, State2);
192 |
193 | /// Cache for 2 immutable states, and 2 parameters.
194 | F2_2 //
195 | cache2states_2params(
196 | F2_2 f) {
197 | WeakContainer? _s1, _s2;
198 | late WeakMap, Result>>> weakMap1;
199 | WeakMap, Result>> weakMap2;
200 |
201 | return (State1 s1, State2 s2) {
202 | return (Param1 p1, Param2 p2) {
203 | var pair = _Pair(p1, p2);
204 | if (_s1 == null || !_s1!.contains(s1) || !_s2!.contains(s2)) {
205 | _s1 = WeakContainer(s1);
206 | _s2 = WeakContainer(s2);
207 |
208 | var result = f(s1, s2)(p1, p2);
209 | weakMap1 = WeakMap();
210 | weakMap2 = WeakMap();
211 | Map<_Pair, Result> map = HashMap();
212 | map[pair] = result;
213 | weakMap2[s2] = map;
214 | weakMap1[s1] = weakMap2;
215 | return result;
216 | }
217 | //
218 | else {
219 | Map<_Pair, Result> map = weakMap1[s1]![s2]!;
220 | if (!map.containsKey(pair)) {
221 | var result = f(s1, s2)(p1, p2);
222 | map[pair] = result;
223 | return result;
224 | }
225 |
226 | return map[pair] as Result;
227 | }
228 | };
229 | };
230 | }
231 |
232 | typedef R2_3 = //
233 | Result Function(Param1, Param2, Param3);
234 |
235 | typedef F2_3 = //
236 | R2_3 Function(State1, State2);
237 |
238 | /// Cache for 2 immutable states, and 3 parameters.
239 | F2_3 //
240 | cache2states_3params(
241 | F2_3 f) {
242 | WeakContainer? _s1, _s2;
243 | late WeakMap, Result>>> weakMap1;
244 | WeakMap, Result>> weakMap2;
245 |
246 | return (State1 s1, State2 s2) {
247 | return (Param1 p1, Param2 p2, Param3 p3) {
248 | var triad = _Triad(p1, p2, p3);
249 | if (_s1 == null || !_s1!.contains(s1) || !_s2!.contains(s2)) {
250 | _s1 = WeakContainer(s1);
251 | _s2 = WeakContainer(s2);
252 |
253 | var result = f(s1, s2)(p1, p2, p3);
254 | weakMap1 = WeakMap();
255 | weakMap2 = WeakMap();
256 | Map<_Triad, Result> map = HashMap();
257 | map[triad] = result;
258 | weakMap2[s2] = map;
259 | weakMap1[s1] = weakMap2;
260 | return result;
261 | }
262 | //
263 | else {
264 | Map<_Triad, Result> map = weakMap1[s1]![s2]!;
265 | if (!map.containsKey(triad)) {
266 | var result = f(s1, s2)(p1, p2, p3);
267 | map[triad] = result;
268 | return result;
269 | }
270 |
271 | return map[triad] as Result;
272 | }
273 | };
274 | };
275 | }
276 |
277 | typedef R3_0 = //
278 | Result Function();
279 |
280 | typedef F3_0 = //
281 | R3_0 Function(State1, State2, State3);
282 |
283 | /// Cache for 3 immutable states, and no parameters.
284 | F3_0 cache3states(
285 | F3_0 f) {
286 | WeakContainer? _s1, _s2, _s3;
287 | late WeakMap>> weakMap1;
288 | WeakMap> weakMap2;
289 | WeakMap weakMap3;
290 |
291 | return (State1 s1, State2 s2, State3 s3) {
292 | return () {
293 | if (_s1 == null ||
294 | _s2 == null ||
295 | _s3 == null || //
296 | !_s1!.contains(s1) ||
297 | !_s2!.contains(s2) ||
298 | !_s3!.contains(s3)) {
299 | _s1 = WeakContainer(s1);
300 | _s2 = WeakContainer(s2);
301 | _s3 = WeakContainer(s3);
302 |
303 | var result = f(s1, s2, s3)();
304 | weakMap1 = WeakMap();
305 | weakMap2 = WeakMap();
306 | weakMap3 = WeakMap();
307 | weakMap3[s3] = result;
308 | weakMap2[s2] = weakMap3;
309 | weakMap1[s1] = weakMap2;
310 | return result;
311 | }
312 | //
313 | else {
314 | return weakMap1[s1]![s2]![s3] as Result;
315 | }
316 | };
317 | };
318 | }
319 |
320 | typedef F1_0_x = R1_0 Function(State1, Extra);
321 |
322 | /// Cache for 1 immutable state, no parameters, and some extra information.
323 | /// Note: The extra information is not used in any way to decide whether
324 | /// the cache should be used/recalculated/evicted.
325 | F1_0_x cache1state_0params_x(
326 | F1_0_x f,
327 | ) {
328 | WeakContainer? _s1;
329 | late WeakMap weakMap;
330 |
331 | return (State1 state1, Extra extra) {
332 | return () {
333 | if (_s1 == null || !_s1!.contains(state1)) {
334 | weakMap = WeakMap();
335 | _s1 = WeakContainer(state1);
336 | var result = f(state1, extra)();
337 | weakMap[state1] = result;
338 | return result;
339 | }
340 | //
341 | else {
342 | return weakMap[state1] as Result;
343 | }
344 | };
345 | };
346 | }
347 |
348 | typedef F2_0_x = R2_0 Function(State1, State2, Extra);
349 |
350 | /// Cache for 2 immutable states, no parameters, and some extra information.
351 | /// Note: The extra information is not used in any way to decide whether
352 | /// the cache should be used/recalculated/evicted.
353 | F2_0_x cache2states_0params_x(
354 | F2_0_x f,
355 | ) {
356 | WeakContainer? _s1, _s2;
357 | late WeakMap> weakMap1;
358 | late WeakMap weakMap2;
359 |
360 | return (State1 state1, State2 state2, Extra extra) {
361 | return () {
362 | if (_s1 == null ||
363 | _s2 == null || //
364 | !_s1!.contains(state1) ||
365 | !_s2!.contains(state2)) {
366 | _s1 = WeakContainer(state1);
367 | _s2 = WeakContainer(state2);
368 |
369 | var result = f(state1, state2, extra)();
370 | weakMap1 = WeakMap();
371 | weakMap2 = WeakMap();
372 | weakMap2[state2] = result;
373 | weakMap1[state1] = weakMap2;
374 | return result;
375 | }
376 | //
377 | else {
378 | return weakMap1[state1]![state2] as Result;
379 | }
380 | };
381 | };
382 | }
383 |
384 | typedef F3_0_x = R3_0 Function(
385 | State1, State2, State3, Extra);
386 |
387 | /// Cache for 3 immutable states, no parameters, and some extra information.
388 | /// Note: The extra information is not used in any way to decide whether
389 | /// the cache should be used/recalculated/evicted.
390 | F3_0_x
391 | cache3states_0params_x(
392 | F3_0_x f,
393 | ) {
394 | WeakContainer? _s1, _s2, _s3;
395 | late WeakMap>> weakMap1;
396 | late WeakMap> weakMap2;
397 | late WeakMap weakMap3;
398 |
399 | return (State1 state1, State2 state2, State3 state3, Extra extra) {
400 | return () {
401 | if (_s1 == null ||
402 | _s2 == null ||
403 | _s3 == null || //
404 | !_s1!.contains(state1) ||
405 | !_s2!.contains(state2) ||
406 | !_s3!.contains(state3)) {
407 | _s1 = WeakContainer(state1);
408 | _s2 = WeakContainer(state2);
409 | _s3 = WeakContainer(state3);
410 |
411 | var result = f(state1, state2, state3, extra)();
412 | weakMap1 = WeakMap();
413 | weakMap2 = WeakMap();
414 | weakMap3 = WeakMap();
415 | weakMap3[state3] = result;
416 | weakMap2[state2] = weakMap3;
417 | weakMap1[state1] = weakMap2;
418 | return result;
419 | }
420 | //
421 | else {
422 | return weakMap1[state1]![state2]![state3] as Result;
423 | }
424 | };
425 | };
426 | }
427 |
428 | class _Pair {
429 | final X x;
430 | final Y y;
431 |
432 | _Pair(this.x, this.y);
433 |
434 | @override
435 | bool operator ==(Object other) =>
436 | identical(this, other) ||
437 | other is _Pair &&
438 | runtimeType == other.runtimeType //
439 | &&
440 | x == other.x &&
441 | y == other.y;
442 |
443 | @override
444 | int get hashCode => Object.hash(x, y);
445 | }
446 |
447 | class _Triad {
448 | final X x;
449 | final Y y;
450 | final Z z;
451 |
452 | _Triad(this.x, this.y, this.z);
453 |
454 | @override
455 | bool operator ==(Object other) =>
456 | identical(this, other) ||
457 | other is _Triad &&
458 | runtimeType == other.runtimeType //
459 | &&
460 | x == other.x &&
461 | y == other.y &&
462 | z == other.z;
463 |
464 | @override
465 | int get hashCode => Object.hash(x, y, z);
466 | }
467 |
--------------------------------------------------------------------------------
/lib/src/weak_container.dart:
--------------------------------------------------------------------------------
1 | /// A weak-container is similar, but not exactly, a weak-reference to some object.
2 | /// Dart doesn't have real weak-references, so the best you can do here is to check
3 | /// that some object is the same you had before.
4 | ///
5 | /// To create a weak-container:
6 | /// ```
7 | /// var obj = Object();
8 | /// var ref = WeakContainer(obj);
9 | /// var someObj = Random().nextBool() ? obj : Object();
10 | /// print(ref.contains(someObj)); // True or false.
11 | /// ```
12 | /// This will print `true` if `someObj` is the same as the original `obj`,
13 | /// and will print `false` if it's a different object, compared by identity.
14 | ///
15 | /// If all references to the original `obj` have been destroyed,
16 | /// the weak-container will NOT prevent `obj` to be garbage-collected.
17 | ///
18 | class WeakContainer {
19 | Expando? _expando;
20 | Object? _value;
21 | bool _isNull;
22 |
23 | WeakContainer(Object? value)
24 | : _expando = _allowedInExpando(value) ? _createExpando(value!) : null,
25 | _value = _allowedInExpando(value) ? null : value,
26 | _isNull = (value == null);
27 |
28 | bool contains(Object? value) {
29 | if (value == null) {
30 | return _isNull;
31 | } else {
32 | if (_value == value) {
33 | return true;
34 | } else {
35 | return (_expando != null && //
36 | _allowedInExpando(value) &&
37 | _expando![value] == true);
38 | }
39 | }
40 | }
41 |
42 | void clear() {
43 | _value = null;
44 | _expando = null;
45 | _isNull = false;
46 | }
47 |
48 | static Expando _createExpando(Object value) {
49 | assert(_allowedInExpando(value));
50 | var expando = Expando();
51 | expando[value] = true;
52 | return expando;
53 | }
54 |
55 | static bool _allowedInExpando(Object? value) =>
56 | value is! String && value is! num && value is! bool && value != null;
57 | }
58 |
--------------------------------------------------------------------------------
/lib/src/weak_map.dart:
--------------------------------------------------------------------------------
1 | /// A WeakMap lets you garbage-collect its keys.
2 | /// Please note: The **[key]** can be garbage-collected, not the [value].
3 | ///
4 | /// This means if you use some object as a key to a map-entry, this alone
5 | /// will not prevent Dart to garbage-collect this object. In other words,
6 | /// after all other references to that object have been destroyed, its entry
7 | /// (key and value) may be removed automatically from the map at any moment.
8 | ///
9 | /// To create a map:
10 | /// ```
11 | /// var map = WeakMap();
12 | /// ```
13 | ///
14 | /// To add and retrieve a value:
15 | /// ```
16 | /// map["John"] = 42;
17 | /// var age = map["John"];
18 | /// ```
19 | ///
20 | /// The following map methods work as expected:
21 | /// ```
22 | /// map.remove("John")
23 | /// map.clear()
24 | /// map.contains("John"))
25 | /// ```
26 | ///
27 | /// However, adding some null value to the map is the same as removing the key:
28 | /// ```
29 | /// map["John"] = null; // Same as map.remove("John")
30 | /// ```
31 | ///
32 | /// Notes:
33 | ///
34 | /// 1. If you use null, a number, a boolean, a String, or a const type as the
35 | /// map key, it will act like a regular map, because these types are never
36 | /// garbage-collected. All other types of object may be garbage-collected.
37 | ///
38 | /// 2. To retrieve a value added to the map, you can use the equivalent
39 | /// syntax `var y = map[x]` or `var y = map.get(x)`.
40 | ///
41 | class WeakMap {
42 | final Map _map;
43 | Expando _expando;
44 |
45 | WeakMap()
46 | : _map = {},
47 | _expando = Expando();
48 |
49 | static bool _allowedInExpando(Object? value) =>
50 | value is! String && value is! num && value is! bool && value != null;
51 |
52 | void operator []=(K key, V value) => add(key: key, value: value);
53 |
54 | /// Returns the value associated with this key, or null if the key doesn't exist in the map.
55 | /// Note this can't differentiate between the key not existing and the value being null.
56 | ///
57 | /// This is the same as using the [get] method.
58 | ///
59 | V? operator [](K key) => get(key);
60 |
61 | void add({required K key, required V value}) {
62 | if (_allowedInExpando(key)) {
63 | _expando[key!] = value;
64 | } else {
65 | _map[key] = value;
66 | }
67 | }
68 |
69 | bool contains(K key) => get(key) != null;
70 |
71 | /// Returns the value associated with this key, or null if the key doesn't exist in the map.
72 | /// Note this can't differentiate between the key not existing and the value being null.
73 | ///
74 | /// This is the same as using the [] operator.
75 | ///
76 | V? get(K key) => _map.containsKey(key)
77 | ? //
78 | _map[key]
79 | : (_allowedInExpando(key) ? _expando[key!] as V : null);
80 |
81 | /// Returns the value associated with this key.
82 | /// It will throw if the key doesn't exist in the map.
83 | /// Note this may only return null if V is nullable.
84 | ///
85 | /// This is the same as using the [] operator.
86 | ///
87 | V getOrThrow(K key) {
88 | if (_map.containsKey(key))
89 | return _map[key] as V;
90 | else {
91 | if (_allowedInExpando(key))
92 | return _expando[key!] as V;
93 | else
94 | throw StateError("No value for key.");
95 | }
96 | }
97 |
98 | void remove(K key) {
99 | _map.remove(key);
100 |
101 | if (_allowedInExpando(key)) {
102 | _expando[key!] = null;
103 | }
104 | }
105 |
106 | void clear() {
107 | _map.clear();
108 | _expando = Expando();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/weak_map.dart:
--------------------------------------------------------------------------------
1 | library weak_map;
2 |
3 | export "src/weak_map.dart";
4 | export "src/weak_container.dart";
5 | export "src/cache.dart";
6 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: weak_map
2 | description: WeakMap is a map where the keys are weakly referenced. WeakContainer lets you check if an object is the same you had before. Cache functions for memoization with weak-references.
3 | version: 4.0.1
4 | # author: Marcelo Glasberg
5 | homepage: https://github.com/marcglasberg/weak_map
6 | topics:
7 | - memory
8 | - memory-management
9 | - cache
10 | - collections
11 | - data-structures
12 |
13 | environment:
14 | sdk: '>=2.19.0 <4.0.0'
15 |
16 | dev_dependencies:
17 | test: ^1.17.12
18 |
--------------------------------------------------------------------------------
/test/cache_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:test/test.dart';
2 | import 'package:weak_map/weak_map.dart';
3 |
4 | void main() {
5 | var stateNames = List.unmodifiable(["Juan", "Anna", "Bill", "Zack", "Arnold", "Amanda"]);
6 |
7 | test('Test 1 state with 0 parameters.', () {
8 | //
9 |
10 | // This is some function we want to cache, for some specific `limit`.
11 | List func(int? limit) {
12 | return (limit == null) ? [] : stateNames.take(limit).toList();
13 | }
14 |
15 | // This is the cache.
16 | var _funcCached = cache1state, int?>((int? limit) => () => func(limit));
17 |
18 | // And this is the cached func.
19 | List funcCached(int? limit) => _funcCached(limit)();
20 |
21 | List memoA1 = funcCached(1);
22 | List memoA2 = funcCached(1);
23 | expect(memoA1, ["Juan"]);
24 | expect(memoA2, ["Juan"]);
25 | expect(identical(memoA1, memoA2), isTrue);
26 |
27 | var memoB1 = _funcCached(2)();
28 | var memoB2 = _funcCached(2)();
29 | expect(memoB1, ["Juan", "Anna"]);
30 | expect(memoB2, ["Juan", "Anna"]);
31 | expect(identical(memoB1, memoB2), isTrue);
32 |
33 | var memoC1 = _funcCached(null)();
34 | var memoC2 = _funcCached(null)();
35 | expect(memoC1, []);
36 | expect(memoC2, []);
37 | expect(identical(memoC1, memoC2), isTrue);
38 | });
39 |
40 | test('Test 1 state with 0 parameters, caching null parameter.', () {
41 | //
42 |
43 | // This is some function we want to cache, for some specific `limit`.
44 | List? func(int? limit) {
45 | return (limit == null) ? null : stateNames.take(limit).toList();
46 | }
47 |
48 | // This is the cache.
49 | var _funcCached = cache1state?, int?>((int? limit) => () => func(limit));
50 |
51 | // And this is the cached func.
52 | List? funcCached(int? limit) => _funcCached(limit)();
53 |
54 | List? memoA1 = funcCached(1);
55 | List? memoA2 = funcCached(1);
56 | expect(memoA1, ["Juan"]);
57 | expect(memoA2, ["Juan"]);
58 | expect(identical(memoA1, memoA2), isTrue);
59 |
60 | var memoB1 = _funcCached(2)();
61 | var memoB2 = _funcCached(2)();
62 | expect(memoB1, ["Juan", "Anna"]);
63 | expect(memoB2, ["Juan", "Anna"]);
64 | expect(identical(memoB1, memoB2), isTrue);
65 |
66 | var memoC1 = _funcCached(null)();
67 | var memoC2 = _funcCached(null)();
68 | expect(memoC1, isNull);
69 | expect(memoC2, isNull);
70 | expect(identical(memoC1, memoC2), isTrue);
71 | });
72 |
73 | test('Test results are forgotten when the state changes (1 state with 0 parameters).', () {
74 | //
75 | var selector = cache1state((int? limit) => () {
76 | return limit == null ? [] : stateNames.take(limit).toList();
77 | });
78 |
79 | var memoA1 = selector(1)();
80 | var memoA2 = selector(1)();
81 | expect(memoA1, ["Juan"]);
82 | expect(identical(memoA1, memoA2), isTrue);
83 |
84 | // Another state with another parameter.
85 | selector(2)();
86 |
87 | // Try reading the previous state, with the same parameter as before.
88 | var memoA5 = selector(1)();
89 | expect(memoA5, ["Juan"]);
90 | expect(identical(memoA5, memoA1), isFalse);
91 | });
92 |
93 | test('Also works when changing to null (1 state with 0 parameters).', () {
94 | //
95 | var selector = cache1state((int? limit) => () {
96 | return limit == null ? [] : stateNames.take(limit).toList();
97 | });
98 |
99 | var memoA1 = selector(1)();
100 | var memoA2 = selector(1)();
101 | expect(memoA1, ["Juan"]);
102 | expect(identical(memoA1, memoA2), isTrue);
103 |
104 | // Another state with another parameter.
105 | selector(null)();
106 |
107 | // Try reading the previous state, with the same parameter as before.
108 | var memoA5 = selector(1)();
109 | expect(memoA5, ["Juan"]);
110 | expect(identical(memoA5, memoA1), isFalse);
111 | });
112 |
113 | test('Also works when changing from null (1 state with 0 parameters).', () {
114 | //
115 | var selector = cache1state((int? limit) => () {
116 | return limit == null ? [] : stateNames.take(limit).toList();
117 | });
118 |
119 | var memoA1 = selector(null)();
120 | var memoA2 = selector(null)();
121 | expect(memoA1, []);
122 | expect(identical(memoA1, memoA2), isTrue);
123 |
124 | // Another state with another parameter.
125 | selector(1)();
126 |
127 | // Try reading the previous state, with the same parameter as before.
128 | var memoA5 = selector(null)();
129 | expect(memoA5, []);
130 | expect(identical(memoA5, memoA1), isFalse);
131 | });
132 |
133 | test('Test 1 state with 1 parameter.', () {
134 | //
135 | var selector = cache1state_1param((List state) => (String startString) {
136 | return state.where((str) => str.startsWith(startString)).toList();
137 | });
138 |
139 | var memoA1 = selector(stateNames)("A");
140 | var memoA2 = selector(stateNames)("A");
141 | expect(memoA1, ["Anna", "Arnold", "Amanda"]);
142 | expect(identical(memoA1, memoA2), isTrue);
143 |
144 | selector(stateNames)("B");
145 |
146 | var memoA3 = selector(stateNames)("A");
147 | expect(memoA3, ["Anna", "Arnold", "Amanda"]);
148 | expect(identical(memoA1, memoA3), isTrue);
149 | });
150 |
151 | test('Test results are forgotten when the state changes (1 state with 1 parameter).', () {
152 | //
153 | var selector = cache1state_1param((List state) => (String startString) {
154 | return state.where((str) => str.startsWith(startString)).toList();
155 | });
156 |
157 | var memoA1 = selector(stateNames)("A");
158 | var memoA2 = selector(stateNames)("A");
159 | expect(memoA1, ["Anna", "Arnold", "Amanda"]);
160 | expect(identical(memoA1, memoA2), isTrue);
161 |
162 | // Another state with another parameter.
163 | selector(List.of(stateNames))("B");
164 | selector(stateNames)("B");
165 |
166 | // Try reading the previous state, with the same parameter as before.
167 | var memoA5 = selector(stateNames)("A");
168 | expect(memoA5, ["Anna", "Arnold", "Amanda"]);
169 | expect(identical(memoA5, memoA1), isFalse);
170 | });
171 |
172 | test('Test 1 state with 2 parameters.', () {
173 | //
174 | var selector =
175 | cache1state_2params((List state) => (String startString, String endString) {
176 | return state
177 | .where((str) => str.startsWith(startString) && str.endsWith(endString))
178 | .toList();
179 | });
180 |
181 | // TODO: MARCELO: Refazer este teste.
182 | // Originalmente era: expect(identical("a", otherA), isFalse);
183 | // With no other changes except
184 | // flutter channel stable 1.22.5 => flutter channel beta 1.24.0-10.2.pre
185 | // (Dart version 2.10.4) => (Dart version 2.12.0 (build 2.12.0-29.10.beta))
186 | // suddenly this isTrue
187 | String otherA = "a" ""; // Concatenate.
188 | expect(identical("a", otherA), isTrue);
189 |
190 | var memoA1 = selector(stateNames)("A", "a");
191 | var memoA2 = selector(stateNames)("A", otherA);
192 | expect(memoA1, ["Anna", "Amanda"]);
193 | expect(identical(memoA1, memoA2), isTrue);
194 |
195 | var memoB1 = selector(stateNames)("A", "d");
196 | var memoB2 = selector(stateNames)("A", "d");
197 | expect(memoB1, ["Arnold"]);
198 | expect(identical(memoB1, memoB2), isTrue);
199 |
200 | var memoA3 = selector(stateNames)("A", "a");
201 | expect(memoA1, ["Anna", "Amanda"]);
202 | expect(identical(memoA1, memoA3), isTrue);
203 | });
204 |
205 | test('Test results are forgotten when the state changes (1 state with 2 parameters).', () {
206 | //
207 | var selector =
208 | cache1state_2params((List state) => (String startString, String endString) {
209 | return state
210 | .where((str) => str.startsWith(startString) && str.endsWith(endString))
211 | .toList();
212 | });
213 |
214 | var memoA1 = selector(stateNames)("A", "a");
215 | var memoA2 = selector(stateNames)("A", "a");
216 | expect(memoA1, ["Anna", "Amanda"]);
217 | expect(identical(memoA1, memoA2), isTrue);
218 |
219 | // Another state with another parameter.
220 | selector(List.of(stateNames))("B", "l");
221 | selector(stateNames)("B", "l");
222 |
223 | // Try reading the previous state, with the same parameter as before.
224 | var memoA5 = selector(stateNames)("A", "a");
225 | expect(memoA5, ["Anna", "Amanda"]);
226 | expect(identical(memoA5, memoA1), isFalse);
227 | });
228 |
229 | test('Test 2 states with 0 parameters.', () {
230 | //
231 | var selector = cache2states((List names, int limit) => () {
232 | return names.where((str) => str.startsWith("A")).take(limit).toList();
233 | });
234 |
235 | var memoA1 = selector(stateNames, 1)();
236 | var memoA2 = selector(stateNames, 1)();
237 | expect(memoA1, ["Anna"]);
238 | expect(memoA2, ["Anna"]);
239 | expect(identical(memoA1, memoA2), isTrue);
240 |
241 | var memoB1 = selector(stateNames, 2)();
242 | var memoB2 = selector(stateNames, 2)();
243 | expect(memoB1, ["Anna", "Arnold"]);
244 | expect(identical(memoB1, memoB2), isTrue);
245 | });
246 |
247 | test('Test results are forgotten when the state changes (2 states with 0 parameters).', () {
248 | //
249 | var selector = cache2states((List names, int limit) => () {
250 | return names.where((str) => str.startsWith("A")).take(limit).toList();
251 | });
252 |
253 | var memoA1 = selector(stateNames, 1)();
254 | var memoA2 = selector(stateNames, 1)();
255 | expect(memoA1, ["Anna"]);
256 | expect(identical(memoA1, memoA2), isTrue);
257 |
258 | // Another state with another parameter.
259 | selector(stateNames, 2)();
260 |
261 | // Try reading the previous state, with the same parameter as before.
262 | var memoA5 = selector(stateNames, 1)();
263 | expect(memoA5, ["Anna"]);
264 | expect(identical(memoA5, memoA1), isFalse);
265 | });
266 |
267 | test('Test 2 states with 1 parameter.', () {
268 | //
269 | var selector = cache2states_1param((List names, int limit) => (String searchString) {
270 | return names.where((str) => str.startsWith(searchString)).take(limit).toList();
271 | });
272 |
273 | var memoA1 = selector(stateNames, 1)("A");
274 | var memoA2 = selector(stateNames, 1)("A");
275 | expect(memoA1, ["Anna"]);
276 | expect(identical(memoA1, memoA2), isTrue);
277 |
278 | var memoB1 = selector(stateNames, 2)("A");
279 | var memoB2 = selector(stateNames, 2)("A");
280 | expect(memoB1, ["Anna", "Arnold"]);
281 | expect(identical(memoB1, memoB2), isTrue);
282 |
283 | var memoC = selector(stateNames, 2)("B");
284 | expect(memoC, ["Bill"]);
285 |
286 | var memoD = selector(stateNames, 2)("A");
287 | expect(identical(memoD, memoB1), isTrue);
288 |
289 | // Has to forget, because the state changed.
290 | selector(stateNames, 1)("A");
291 | expect(identical(memoA1, memoC), isFalse);
292 | });
293 |
294 | test('Test results are forgotten when the state changes (2 states with 1 parameter).', () {
295 | //
296 | var selector = cache2states_1param((List names, int limit) => (String searchString) {
297 | return names.where((str) => str.startsWith(searchString)).take(limit).toList();
298 | });
299 |
300 | var memoA1 = selector(stateNames, 1)("A");
301 | var memoA2 = selector(stateNames, 1)("A");
302 | expect(memoA1, ["Anna"]);
303 | expect(identical(memoA1, memoA2), isTrue);
304 |
305 | // Another state with another parameter.
306 | selector(stateNames, 2)("B");
307 | selector(stateNames, 1)("B");
308 |
309 | // Try reading the previous state, with the same parameter as before.
310 | var memoA5 = selector(stateNames, 1)("A");
311 | expect(memoA5, ["Anna"]);
312 | expect(identical(memoA5, memoA1), isFalse);
313 | });
314 |
315 | test('Test 2 states with 2 parameters.', () {
316 | //
317 | var selector = cache2states_2params(
318 | (List names, int limit) => (String startString, String endString) {
319 | return names
320 | .where((str) => str.startsWith(startString) && str.endsWith(endString))
321 | .take(limit)
322 | .toList();
323 | });
324 |
325 | var memoA1 = selector(stateNames, 1)("A", "a");
326 | var memoA2 = selector(stateNames, 1)("A", "a");
327 | expect(memoA1, ["Anna"]);
328 | expect(identical(memoA1, memoA2), isTrue);
329 |
330 | var memoB1 = selector(stateNames, 2)("A", "a");
331 | var memoB2 = selector(stateNames, 2)("A", "a");
332 | expect(memoB1, ["Anna", "Amanda"]);
333 | expect(identical(memoB1, memoB2), isTrue);
334 | });
335 |
336 | test('Test results are forgotten when the state changes (2 states with 2 parameters).', () {
337 | //
338 | var selector = cache2states_2params(
339 | (List names, int limit) => (String startString, String endString) {
340 | return names
341 | .where((str) => str.startsWith(startString) && str.endsWith(endString))
342 | .take(limit)
343 | .toList();
344 | });
345 |
346 | var memoA1 = selector(stateNames, 1)("A", "a");
347 | var memoA2 = selector(stateNames, 1)("A", "a");
348 | expect(memoA1, ["Anna"]);
349 | expect(identical(memoA1, memoA2), isTrue);
350 |
351 | // Another state with another parameter.
352 | selector(stateNames, 2)("B", "l");
353 | selector(stateNames, 1)("B", "l");
354 |
355 | // Try reading the previous state, with the same parameter as before.
356 | var memoA5 = selector(stateNames, 1)("A", "a");
357 | expect(memoA5, ["Anna"]);
358 | expect(identical(memoA5, memoA1), isFalse);
359 | });
360 |
361 | test(
362 | 'Changing the second or the first state, it should forget the cached value. '
363 | '(2 states with 2 parameters)', () {
364 | //
365 | var stateNames1 = List.unmodifiable(["A1a", "A2a", "A3x", "B4a", "B5a", "B6x"]);
366 |
367 | var selector = cache2states_2params(
368 | (List names, int limit) => (String startString, String endString) {
369 | return names
370 | .where((str) => str.startsWith(startString) && str.endsWith(endString))
371 | .take(limit)
372 | .toList();
373 | });
374 |
375 | var memo1 = selector(stateNames1, 1)("A", "a");
376 | expect(memo1, ["A1a"]);
377 |
378 | var memo2 = selector(stateNames1, 2)("A", "a");
379 | expect(memo2, ["A1a", "A2a"]);
380 |
381 | var memo3 = selector(stateNames1, 1)("A", "a");
382 | expect(memo3, ["A1a"]);
383 |
384 | var memo4 = selector(stateNames1, 2)("A", "a");
385 | expect(memo4, ["A1a", "A2a"]);
386 |
387 | expect(identical(memo1, memo3), isFalse);
388 | expect(identical(memo2, memo4), isFalse);
389 |
390 | // ---
391 |
392 | var stateNames2 = List.unmodifiable(["A1a", "A2a", "A3x", "B4a", "B5a", "B6x"]);
393 |
394 | var memo5 = selector(stateNames1, 1)("A", "a");
395 | expect(memo5, ["A1a"]);
396 |
397 | var memo6 = selector(stateNames2, 1)("A", "a");
398 | expect(memo6, ["A1a"]);
399 |
400 | var memo7 = selector(stateNames1, 1)("A", "a");
401 | expect(memo7, ["A1a"]);
402 |
403 | var memo8 = selector(stateNames2, 1)("A", "a");
404 | expect(memo8, ["A1a"]);
405 |
406 | expect(identical(memo5, memo7), isFalse);
407 | expect(identical(memo6, memo8), isFalse);
408 | });
409 |
410 | test('Test 2 states with 3 parameters.', () {
411 | //
412 | var selector = cache2states_3params((List names, int limit) =>
413 | (String startString, String endString, String containsString) {
414 | return names
415 | .where((str) =>
416 | str.startsWith(startString) &&
417 | str.endsWith(endString) &&
418 | str.contains(containsString))
419 | .take(limit)
420 | .toList();
421 | });
422 |
423 | expect(selector(stateNames, 1)("A", "a", "x"), []);
424 |
425 | var memoA1 = selector(stateNames, 1)("A", "a", "n");
426 | var memoA2 = selector(stateNames, 1)("A", "a", "n");
427 | expect(memoA1, ["Anna"]);
428 | expect(identical(memoA1, memoA2), isTrue);
429 |
430 | var memoB1 = selector(stateNames, 2)("A", "a", "n");
431 | var memoB2 = selector(stateNames, 2)("A", "a", "n");
432 | expect(memoB1, ["Anna", "Amanda"]);
433 | expect(identical(memoB1, memoB2), isTrue);
434 | });
435 |
436 | test('Test results are forgotten when the state changes (2 states with 3 parameters).', () {
437 | //
438 | var selector = cache2states_3params((List names, int limit) =>
439 | (String startString, String endString, String containsString) {
440 | return names
441 | .where((str) =>
442 | str.startsWith(startString) &&
443 | str.endsWith(endString) &&
444 | str.contains(containsString))
445 | .take(limit)
446 | .toList();
447 | });
448 |
449 | var memoA1 = selector(stateNames, 1)("A", "a", "n");
450 | var memoA2 = selector(stateNames, 1)("A", "a", "n");
451 | expect(memoA1, ["Anna"]);
452 | expect(identical(memoA1, memoA2), isTrue);
453 |
454 | // Same state with another parameter.
455 | // Then try reading the previous state, with the same parameter as before.
456 | // Result is the same instance.
457 | var memoA3 = selector(stateNames, 1)("A", "a", "x");
458 | var memoA4 = selector(stateNames, 1)("A", "a", "n");
459 | var memoA5 = selector(stateNames, 1)("A", "a", "n");
460 | var memoA6 = selector(stateNames, 1)("A", "a", "x");
461 | expect(memoA3, []);
462 | expect(memoA4, ["Anna"]);
463 | expect(memoA5, ["Anna"]);
464 | expect(memoA6, []);
465 | expect(identical(memoA4, memoA5), isTrue);
466 | expect(identical(memoA3, memoA6), isTrue);
467 |
468 | // ---
469 |
470 | // Another state with the same parameter.
471 | selector(stateNames, 2)("A", "a", "n");
472 |
473 | // Try reading the previous state, with the same parameter as before.
474 | var memoA7 = selector(stateNames, 1)("A", "a", "x");
475 | var memoA8 = selector(stateNames, 1)("A", "a", "n");
476 | expect(memoA7, []);
477 | expect(memoA8, ["Anna"]);
478 | expect(identical(memoA7, memoA3), isFalse);
479 | expect(identical(memoA8, memoA4), isFalse);
480 | });
481 |
482 | test(
483 | 'Changing the second or the first state, it should forget the cached value. '
484 | '(2 states with 3 parameters)', () {
485 | //
486 | var stateNames1 = List.unmodifiable(["A1na", "A2na", "A3nx", "B4na", "B5na", "B6nx"]);
487 |
488 | var selector = cache2states_3params((List names, int limit) =>
489 | (String startString, String endString, String containsString) {
490 | return names
491 | .where((str) =>
492 | str.startsWith(startString) &&
493 | str.endsWith(endString) &&
494 | str.contains(containsString))
495 | .take(limit)
496 | .toList();
497 | });
498 |
499 | var memo1 = selector(stateNames1, 1)("A", "a", "n");
500 | expect(memo1, ["A1na"]);
501 |
502 | var memo2 = selector(stateNames1, 2)("A", "a", "n");
503 | expect(memo2, ["A1na", "A2na"]);
504 |
505 | var memo3 = selector(stateNames1, 1)("A", "a", "n");
506 | expect(memo3, ["A1na"]);
507 |
508 | var memo4 = selector(stateNames1, 2)("A", "a", "n");
509 | expect(memo4, ["A1na", "A2na"]);
510 |
511 | expect(identical(memo1, memo3), isFalse);
512 | expect(identical(memo2, memo4), isFalse);
513 |
514 | // ---
515 |
516 | var stateNames2 = List.unmodifiable(["A1na", "A2na", "A3nx", "B4na", "B5na", "B6nx"]);
517 |
518 | var memo5 = selector(stateNames1, 1)("A", "a", "n");
519 | expect(memo5, ["A1na"]);
520 |
521 | var memo6 = selector(stateNames2, 1)("A", "a", "n");
522 | expect(memo6, ["A1na"]);
523 |
524 | var memo7 = selector(stateNames1, 1)("A", "a", "n");
525 | expect(memo7, ["A1na"]);
526 |
527 | var memo8 = selector(stateNames2, 1)("A", "a", "n");
528 | expect(memo8, ["A1na"]);
529 |
530 | expect(identical(memo5, memo7), isFalse);
531 | expect(identical(memo6, memo8), isFalse);
532 | });
533 |
534 | test('Test 3 states with 0 parameters.', () {
535 | //
536 | var selector = cache3states((
537 | List names,
538 | int limit,
539 | String startsWith,
540 | ) =>
541 | () {
542 | return names.where((str) => str.startsWith(startsWith)).take(limit).toList();
543 | });
544 |
545 | // Same states return the same object as the result.
546 | var memoA1 = selector(stateNames, 1, "A")();
547 | var memoA2 = selector(stateNames, 1, "A")();
548 | expect(memoA1, ["Anna"]);
549 | expect(memoA2, ["Anna"]);
550 | expect(identical(memoA1, memoA2), isTrue);
551 |
552 | // ---
553 |
554 | // Change first state (even if it's equal), deletes the cache.
555 | var memoA = selector(stateNames, 1, "A")();
556 | var memoB = selector(stateNames.toList(), 1, "A")();
557 | expect(memoB, ["Anna"]);
558 | expect(identical(memoA, memoB), isFalse);
559 |
560 | // ---
561 |
562 | // Change first state, deletes the cache.
563 | memoA = selector(stateNames, 1, "A")();
564 | selector(stateNames.toList(), 1, "A")();
565 | memoB = selector(stateNames, 1, "A")();
566 | expect(memoB, ["Anna"]);
567 | expect(identical(memoA, memoB), isFalse);
568 |
569 | // Change second state, deletes the cache.
570 | memoA = selector(stateNames, 1, "A")();
571 | selector(stateNames, 2, "A")();
572 | memoB = selector(stateNames, 1, "A")();
573 | expect(memoB, ["Anna"]);
574 | expect(identical(memoA, memoB), isFalse);
575 |
576 | // Change third state, deletes the cache.
577 | memoA = selector(stateNames, 1, "A")();
578 | selector(stateNames, 1, "B")();
579 | memoB = selector(stateNames, 1, "A")();
580 | expect(memoB, ["Anna"]);
581 | expect(identical(memoA, memoB), isFalse);
582 | });
583 | }
584 |
--------------------------------------------------------------------------------
/test/weak_container_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:test/test.dart';
2 | import 'package:weak_map/weak_map.dart';
3 |
4 | void main() {
5 | test("Contains.", () {
6 | expect(WeakContainer(1).contains(1), true);
7 | expect(WeakContainer(1).contains(2), false);
8 | expect(WeakContainer(1).contains("A"), false);
9 | expect(WeakContainer(true).contains(null), false);
10 |
11 | expect(WeakContainer("A").contains("A"), true);
12 | expect(WeakContainer("A").contains("B"), false);
13 | expect(WeakContainer("A").contains(1), false);
14 | expect(WeakContainer(true).contains(null), false);
15 |
16 | expect(WeakContainer(true).contains(true), true);
17 | expect(WeakContainer(true).contains(false), false);
18 | expect(WeakContainer(true).contains("A"), false);
19 | expect(WeakContainer(true).contains(null), false);
20 |
21 | expect(WeakContainer(null).contains(null), true);
22 | expect(WeakContainer(null).contains(true), false);
23 | expect(WeakContainer(null).contains(false), false);
24 | expect(WeakContainer(null).contains("A"), false);
25 | expect(WeakContainer(null).contains(1), false);
26 |
27 | var obj1 = Object();
28 | var obj2 = Object();
29 | expect(WeakContainer(obj1).contains(obj1), true);
30 | expect(WeakContainer(obj1).contains(obj2), false);
31 | expect(WeakContainer(obj1).contains(1), false);
32 | expect(WeakContainer(obj1).contains("A"), false);
33 | expect(WeakContainer(obj1).contains(null), false);
34 | expect(WeakContainer(1).contains(obj1), false);
35 | expect(WeakContainer("A").contains(obj1), false);
36 | expect(WeakContainer(true).contains(obj1), false);
37 | expect(WeakContainer(false).contains(obj1), false);
38 | expect(WeakContainer(null).contains(obj1), false);
39 | });
40 |
41 | test("Clear.", () {
42 | var container = WeakContainer(1);
43 | expect(container.contains(1), true);
44 | container.clear();
45 | expect(container.contains(1), false);
46 | });
47 |
48 | test("Using null in the map.", () {
49 | var map = WeakMap();
50 |
51 | var obj1 = Object();
52 | expect(map[null], null);
53 | expect(map.contains(null), false);
54 |
55 | map[null] = obj1;
56 | expect(map[null], obj1);
57 | expect(map.contains(null), true);
58 |
59 | map[null] = null;
60 | expect(map[null], null);
61 | expect(map.contains(null), false);
62 | });
63 |
64 | test("Using null in the map.", () async {
65 | var map = WeakMap();
66 |
67 | var obj1 = Object();
68 | expect(map[null], null);
69 | expect(map.contains(null), false);
70 |
71 | map[null] = obj1;
72 | expect(map[null], obj1);
73 | expect(map.contains(null), true);
74 |
75 | map[null] = null;
76 | expect(map[null], null);
77 | expect(map.contains(null), false);
78 | });
79 | }
80 |
--------------------------------------------------------------------------------
/test/weak_map_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:test/test.dart';
2 | import 'package:weak_map/weak_map.dart';
3 |
4 | void main() {
5 | test("Add number/String/boolean to map.", () {
6 | var map = WeakMap();
7 |
8 | var obj1 = Object();
9 | var obj2 = Object();
10 | var obj3 = Object();
11 | var obj4 = Object();
12 | var obj5 = Object();
13 | expect(obj1, isNot(obj2));
14 | expect(obj2, isNot(obj3));
15 | expect(obj3, isNot(obj4));
16 | expect(obj4, isNot(obj5));
17 |
18 | map[1] = obj1;
19 | map["A"] = obj2;
20 | map[true] = obj3;
21 | map[false] = obj4;
22 | map[null] = obj5;
23 |
24 | expect(map[1], obj1);
25 | expect(map["A"], obj2);
26 | expect(map[true], obj3);
27 | expect(map[false], obj4);
28 | expect(map[null], obj5);
29 | });
30 |
31 | test("Using null as the map value.", () {
32 | var map = WeakMap();
33 |
34 | map["A"] = null;
35 | expect(map.contains("A"), false);
36 |
37 | map["A"] = Object();
38 | expect(map.contains("A"), true);
39 |
40 | map["A"] = null;
41 | expect(map.contains("A"), false);
42 |
43 | map["A"] = Object();
44 | map.remove("A");
45 | expect(map.contains("A"), false);
46 | });
47 |
48 | test("Using null as the map key.", () {
49 | var map = WeakMap();
50 |
51 | var obj1 = Object();
52 | expect(map[null], null);
53 | expect(map.contains(null), false);
54 |
55 | map[null] = obj1;
56 | expect(map[null], obj1);
57 | expect(map.contains(null), true);
58 |
59 | map[null] = null;
60 | expect(map[null], null);
61 | expect(map.contains(null), false);
62 | });
63 |
64 | test("Using null in the map.", () async {
65 | var map = WeakMap();
66 |
67 | var obj1 = Object();
68 | expect(map[null], null);
69 | expect(map.contains(null), false);
70 |
71 | map[null] = obj1;
72 | expect(map[null], obj1);
73 | expect(map.contains(null), true);
74 |
75 | map[null] = null;
76 | expect(map[null], null);
77 | expect(map.contains(null), false);
78 | });
79 |
80 | test("getOrThrow", () async {
81 | //
82 | var map = WeakMap();
83 | map[1] = 1;
84 | map["A"] = 2;
85 | map[true] = 3;
86 | var obj1 = Object();
87 | map[obj1] = 4;
88 | var obj2 = Object();
89 | map[obj2] = 5;
90 |
91 | expect(map[1], 1);
92 | expect(map.get(1), 1);
93 | expect(map.getOrThrow(1), 1);
94 |
95 | expect(map["A"], 2);
96 | expect(map.get("A"), 2);
97 | expect(map.getOrThrow("A"), 2);
98 |
99 | expect(map[true], 3);
100 | expect(map.get(true), 3);
101 | expect(map.getOrThrow(true), 3);
102 |
103 | expect(map[obj1], 4);
104 | expect(map.get(obj1), 4);
105 | expect(map.getOrThrow(obj1), 4);
106 |
107 | expect(map[obj2], 5);
108 | expect(map.get(obj2), 5);
109 | expect(map.getOrThrow(obj2), 5);
110 |
111 | expect(map[123], null);
112 | expect(map.get(123), null);
113 | expect(() => map.getOrThrow(123), throwsStateError);
114 | });
115 | }
116 |
--------------------------------------------------------------------------------