weak 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/LICENSE.txt +21 -0
- data/README.md +232 -0
- data/lib/weak/map/abstract_strong_keys.rb +87 -0
- data/lib/weak/map/deletable.rb +65 -0
- data/lib/weak/map/strong_keys.rb +186 -0
- data/lib/weak/map/strong_secondary_keys.rb +229 -0
- data/lib/weak/map/weak_keys.rb +134 -0
- data/lib/weak/map/weak_keys_with_delete.rb +126 -0
- data/lib/weak/map.rb +714 -0
- data/lib/weak/set/strong_keys.rb +123 -0
- data/lib/weak/set/strong_secondary_keys.rb +154 -0
- data/lib/weak/set/weak_keys.rb +107 -0
- data/lib/weak/set/weak_keys_with_delete.rb +94 -0
- data/lib/weak/set.rb +749 -0
- data/lib/weak/version.rb +14 -0
- data/lib/weak.rb +45 -0
- metadata +65 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) Holger Just
|
4
|
+
#
|
5
|
+
# This software may be modified and distributed under the terms
|
6
|
+
# of the MIT license. See the LICENSE.txt file for details.
|
7
|
+
|
8
|
+
require "set"
|
9
|
+
|
10
|
+
require "weak/map/abstract_strong_keys"
|
11
|
+
|
12
|
+
##
|
13
|
+
module Weak
|
14
|
+
class Map
|
15
|
+
# This {Weak::Map} strategy targets JRuby >= 9.4.6.0 and TruffleRuby >= 22.
|
16
|
+
# Older versions require additional indirections implemented in
|
17
|
+
# {StrongSecondaryKeys}:
|
18
|
+
#
|
19
|
+
# - https://github.com/jruby/jruby/issues/7862
|
20
|
+
# - https://github.com/oracle/truffleruby/issues/2267
|
21
|
+
#
|
22
|
+
# The `ObjectSpace::WeakMap` on JRuby and TruffleRuby has strong keys and
|
23
|
+
# weak values. Thus, only the value object in an `ObjectSpace::WeakMap` can
|
24
|
+
# be garbage collected to remove the entry while the key defines a strong
|
25
|
+
# object reference which prevents the key object from being garbage
|
26
|
+
# collected.
|
27
|
+
#
|
28
|
+
# As a workaround, we use the element's object_id as a key. Being an
|
29
|
+
# `Integer`, the object_id is generally is not garbage collected anyway but
|
30
|
+
# allows to uniquely identity the object.
|
31
|
+
#
|
32
|
+
# As we need to store both a key and value object for each key-value pair in
|
33
|
+
# our `Weak::Map`, we use two separate `ObjectSpace::WeakMap` objects for
|
34
|
+
# storing those. This allows keys and values to be independently garbage
|
35
|
+
# collected. When accessing a logical key in the {Weak::Map}, we need to
|
36
|
+
# manually check if we have a valid entry for both the stored key and the
|
37
|
+
# associated value.
|
38
|
+
#
|
39
|
+
# The `ObjectSpace::WeakMap` does not allow to explicitly delete entries. We
|
40
|
+
# emulate this by setting the garbage-collectible value of a deleted entry
|
41
|
+
# to a simple new object. This value will be garbage collected on the next
|
42
|
+
# GC run which will then remove the entry. When accessing elements, we
|
43
|
+
# delete and filter out these recently deleted entries.
|
44
|
+
module StrongKeys
|
45
|
+
include AbstractStrongKeys
|
46
|
+
|
47
|
+
# Checks if this strategy is usable for the current Ruby version.
|
48
|
+
#
|
49
|
+
# @return [Bool] truethy for Ruby, TruffleRuby and modern JRuby, falsey
|
50
|
+
# otherwise
|
51
|
+
def self.usable?
|
52
|
+
case RUBY_ENGINE
|
53
|
+
when "ruby", "truffleruby"
|
54
|
+
true
|
55
|
+
when "jruby"
|
56
|
+
Gem::Version.new(RUBY_ENGINE_VERSION) >= Gem::Version.new("9.4.6.0")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @!macro weak_map_accessor_read
|
61
|
+
def [](key)
|
62
|
+
_get(key.__id__) { _default(key) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# @!macro weak_map_accessor_write
|
66
|
+
def []=(key, value)
|
67
|
+
id = key.__id__
|
68
|
+
|
69
|
+
@keys[id] = key.nil? ? NIL : key
|
70
|
+
@values[id] = value.nil? ? NIL : value
|
71
|
+
value
|
72
|
+
end
|
73
|
+
|
74
|
+
# @!macro weak_map_method_clear
|
75
|
+
def clear
|
76
|
+
@keys = ObjectSpace::WeakMap.new
|
77
|
+
@values = ObjectSpace::WeakMap.new
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!macro weak_map_method_delete
|
82
|
+
def delete(key)
|
83
|
+
_delete(key.__id__) { yield(key) if block_given? }
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!macro weak_map_method_each_key
|
87
|
+
def each_key
|
88
|
+
return enum_for(__method__) { size } unless block_given?
|
89
|
+
|
90
|
+
@keys.values.each do |raw_key|
|
91
|
+
next if DeletedEntry === raw_key
|
92
|
+
|
93
|
+
key = value!(raw_key)
|
94
|
+
id = key.__id__
|
95
|
+
if missing?(@values[id])
|
96
|
+
@keys[id] = DeletedEntry.new
|
97
|
+
else
|
98
|
+
yield key
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# @!macro weak_map_method_each_pair
|
106
|
+
def each_pair
|
107
|
+
return enum_for(__method__) { size } unless block_given?
|
108
|
+
|
109
|
+
@keys.values.each do |raw_key|
|
110
|
+
next if DeletedEntry === raw_key
|
111
|
+
|
112
|
+
key = value!(raw_key)
|
113
|
+
id = key.__id__
|
114
|
+
|
115
|
+
raw_value = @values[id]
|
116
|
+
if missing?(raw_value)
|
117
|
+
@keys[id] = DeletedEntry.new
|
118
|
+
else
|
119
|
+
yield [key, value!(raw_value)]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# @!macro weak_map_method_each_value
|
127
|
+
def each_value
|
128
|
+
return enum_for(__method__) { size } unless block_given?
|
129
|
+
|
130
|
+
@keys.values.each do |raw_key|
|
131
|
+
next if DeletedEntry === raw_key
|
132
|
+
|
133
|
+
key = value!(raw_key)
|
134
|
+
id = key.__id__
|
135
|
+
|
136
|
+
raw_value = @values[id]
|
137
|
+
if missing?(raw_value)
|
138
|
+
@keys[id] = DeletedEntry.new
|
139
|
+
else
|
140
|
+
yield value!(raw_value)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!macro weak_map_method_fetch
|
148
|
+
def fetch(key, default = UNDEFINED, &block)
|
149
|
+
_get(key.__id__) { _fetch_default(key, default, &block) }
|
150
|
+
end
|
151
|
+
|
152
|
+
# @!macro weak_map_method_include_question
|
153
|
+
def include?(key)
|
154
|
+
_get(key.__id__) { return false }
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
# @!macro weak_map_method_prune
|
159
|
+
def prune
|
160
|
+
value_keys = ::Set.new(@values.keys)
|
161
|
+
|
162
|
+
@keys.keys.each do |id|
|
163
|
+
next if value_keys.delete?(id)
|
164
|
+
@keys[id] = DeletedEntry.new
|
165
|
+
end
|
166
|
+
|
167
|
+
value_keys.each do |id|
|
168
|
+
@values[id] = DeletedEntry.new
|
169
|
+
end
|
170
|
+
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def auto_prune
|
177
|
+
s1 = @keys.size
|
178
|
+
s2 = @values.size
|
179
|
+
s1, s2 = s2, s1 if s1 < s2
|
180
|
+
|
181
|
+
cutoff = [2000, (s1 * 0.2).ceil].max
|
182
|
+
prune unless s1 - s2 > cutoff
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) Holger Just
|
4
|
+
#
|
5
|
+
# This software may be modified and distributed under the terms
|
6
|
+
# of the MIT license. See the LICENSE.txt file for details.
|
7
|
+
|
8
|
+
require "set"
|
9
|
+
|
10
|
+
require "weak/map/abstract_strong_keys"
|
11
|
+
|
12
|
+
##
|
13
|
+
module Weak
|
14
|
+
class Map
|
15
|
+
# This {Weak::Map} strategy targets JRuby < 9.4.6.0.
|
16
|
+
#
|
17
|
+
# These JRuby versions have a similar `ObjectSpace::WeakMap` as newer
|
18
|
+
# JRubies with strong keys and weak values. Thus, only the value object can
|
19
|
+
# be garbage collected to remove the entry while the key defines a strong
|
20
|
+
# object reference which prevents the key object from being garbage
|
21
|
+
# collected.
|
22
|
+
#
|
23
|
+
# As we need to store both a key and value object for each key-value pair in
|
24
|
+
# our `Weak::Map`, we use two separate `ObjectSpace::WeakMap` objects for
|
25
|
+
# storing those. This allows keys and values to be independently garbage
|
26
|
+
# collected. When accessing a logical key in the {Weak::Map}, we need to
|
27
|
+
# manually check if we have a valid entry for both the stored key and the
|
28
|
+
# associated value.
|
29
|
+
#
|
30
|
+
# Additionally, `Integer` values (including object_ids) can have multiple
|
31
|
+
# different object representations in JRuby, making them not strictly equal.
|
32
|
+
# Thus, we can not use the object_id as a key in an `ObjectSpace::WeakMap`
|
33
|
+
# as we do in {Weak::Map::StrongKeys} for newer JRuby versions.
|
34
|
+
#
|
35
|
+
# As a workaround we use a more indirect implementation with a secondary
|
36
|
+
# lookup table for the `ObjectSpace::WeakMap` keys which is inspired by
|
37
|
+
# [Google::Protobuf::Internal::LegacyObjectCache](https://github.com/protocolbuffers/protobuf/blob/afe2de261861717026c3b57ec83678590d5de838/ruby/lib/google/protobuf/internal/object_cache.rb#L42-L96)
|
38
|
+
#
|
39
|
+
# This secondary key map is a regular Hash which stores a mapping from the
|
40
|
+
# key's object_id to a separate Object which in turn is used as a key
|
41
|
+
# in the `ObjectSpace::WeakMap` for the stored keys and values.
|
42
|
+
#
|
43
|
+
# Being a regular Hash, the keys and values of the secondary key map are not
|
44
|
+
# automatically garbage collected as elements in the `ObjectSpace::WeakMap`
|
45
|
+
# are removed. However, its entries are rather cheap with Integer keys and
|
46
|
+
# "empty" objects as values.
|
47
|
+
#
|
48
|
+
# As this strategy is the most conservative with the fewest requirements to
|
49
|
+
# the `ObjectSpace::WeakMap`, we use it as a default or fallback if there is
|
50
|
+
# no better strategy.
|
51
|
+
module StrongSecondaryKeys
|
52
|
+
include AbstractStrongKeys
|
53
|
+
|
54
|
+
# Checks if this strategy is usable for the current Ruby version.
|
55
|
+
#
|
56
|
+
# @return [Bool] always `true` to indicate that this stragegy should be
|
57
|
+
# usable with any Ruby implementation which provides an
|
58
|
+
# `ObjectSpace::WeakMap`.
|
59
|
+
def self.usable?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
# @!macro weak_map_accessor_read
|
64
|
+
def [](key)
|
65
|
+
id = @key_map[key.__id__]
|
66
|
+
unless id
|
67
|
+
auto_prune
|
68
|
+
return _default(key)
|
69
|
+
end
|
70
|
+
|
71
|
+
_get(id) { _default(key) }
|
72
|
+
end
|
73
|
+
|
74
|
+
# @!macro weak_map_accessor_write
|
75
|
+
def []=(key, value)
|
76
|
+
id = @key_map[key.__id__] ||= Object.new.freeze
|
77
|
+
|
78
|
+
@keys[id] = key.nil? ? NIL : key
|
79
|
+
@values[id] = value.nil? ? NIL : value
|
80
|
+
value
|
81
|
+
end
|
82
|
+
|
83
|
+
# @!macro weak_map_method_clear
|
84
|
+
def clear
|
85
|
+
@keys = ObjectSpace::WeakMap.new
|
86
|
+
@values = ObjectSpace::WeakMap.new
|
87
|
+
@key_map = {}
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# @!macro weak_map_method_delete
|
92
|
+
def delete(key)
|
93
|
+
id = @key_map[key.__id__]
|
94
|
+
return block_given? ? yield(key) : nil unless id
|
95
|
+
|
96
|
+
_delete(id) { yield(key) if block_given? }
|
97
|
+
end
|
98
|
+
|
99
|
+
# @!macro weak_map_method_each_key
|
100
|
+
def each_key
|
101
|
+
return enum_for(__method__) { size } unless block_given?
|
102
|
+
|
103
|
+
@keys.values.each do |raw_key|
|
104
|
+
next if DeletedEntry === raw_key
|
105
|
+
|
106
|
+
key = value!(raw_key)
|
107
|
+
next unless (id = @key_map[key.__id__])
|
108
|
+
if missing?(@values[id])
|
109
|
+
@keys[id] = DeletedEntry.new
|
110
|
+
else
|
111
|
+
yield key
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# @!macro weak_map_method_each_pair
|
119
|
+
def each_pair
|
120
|
+
return enum_for(__method__) { size } unless block_given?
|
121
|
+
|
122
|
+
@keys.values.each do |raw_key|
|
123
|
+
next if DeletedEntry === raw_key
|
124
|
+
|
125
|
+
key = value!(raw_key)
|
126
|
+
next unless (id = @key_map[key.__id__])
|
127
|
+
|
128
|
+
raw_value = @values[id]
|
129
|
+
if missing?(raw_value)
|
130
|
+
@keys[id] = DeletedEntry.new
|
131
|
+
else
|
132
|
+
yield [key, value!(raw_value)]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# @!macro weak_map_method_each_value
|
140
|
+
def each_value
|
141
|
+
return enum_for(__method__) { size } unless block_given?
|
142
|
+
|
143
|
+
@keys.values.each do |raw_key|
|
144
|
+
next if DeletedEntry === raw_key
|
145
|
+
|
146
|
+
key = value!(raw_key)
|
147
|
+
next unless (id = @key_map[key.__id__])
|
148
|
+
|
149
|
+
raw_value = @values[id]
|
150
|
+
if missing?(raw_value)
|
151
|
+
@keys[id] = DeletedEntry.new
|
152
|
+
else
|
153
|
+
yield value!(raw_value)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
# @!macro weak_map_method_fetch
|
161
|
+
def fetch(key, default = UNDEFINED, &block)
|
162
|
+
id = @key_map[key.__id__]
|
163
|
+
unless id
|
164
|
+
auto_prune
|
165
|
+
return _fetch_default(key, default, &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
_get(id) { _fetch_default(key, default, &block) }
|
169
|
+
end
|
170
|
+
|
171
|
+
# @!macro weak_map_method_include_question
|
172
|
+
def include?(key)
|
173
|
+
id = @key_map[key.__id__]
|
174
|
+
unless id
|
175
|
+
auto_prune
|
176
|
+
return false
|
177
|
+
end
|
178
|
+
|
179
|
+
_get(id) { return false }
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
# @!macro weak_map_method_prune
|
184
|
+
def prune
|
185
|
+
orphaned_value_keys = ::Set.new(@values.keys)
|
186
|
+
remaining_keys = ::Set.new
|
187
|
+
|
188
|
+
@keys.keys.each do |id|
|
189
|
+
if orphaned_value_keys.delete?(id)
|
190
|
+
# Here, we have found a valid value belonging to the key. As both
|
191
|
+
# key and value are valid, we keep the @key_map entry.
|
192
|
+
remaining_keys << id
|
193
|
+
else
|
194
|
+
# Here, the value was missing (i.e. garbage collected). We mark the
|
195
|
+
# still present key as deleted
|
196
|
+
@keys[id] = DeletedEntry.new
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Mark all (remaining) values as deleted for which we have not found a
|
201
|
+
# matching key above
|
202
|
+
orphaned_value_keys.each do |id|
|
203
|
+
@values[id] = DeletedEntry.new
|
204
|
+
end
|
205
|
+
|
206
|
+
# Finally, remove all @key_map entries for which we have not seen a
|
207
|
+
# valid key and value above
|
208
|
+
@key_map.keep_if { |_, id| remaining_keys.include?(id) }
|
209
|
+
|
210
|
+
self
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
# prune unneeded entries from the `@key_map` Hash as well as
|
216
|
+
# garbage-collected entries from `@keys` and `@values` if we could remove
|
217
|
+
# at least 2000 entries or 20% of the table size (whichever is greater).
|
218
|
+
# Since the cost of the GC pass is O(N), we want to make sure that we
|
219
|
+
# condition this on overall table size, to avoid O(N^2) CPU costs.
|
220
|
+
def auto_prune
|
221
|
+
key_map_size = @key_map.size
|
222
|
+
cutoff = [2000, (key_map_size * 0.2).ceil].max
|
223
|
+
key_value_size = [@keys.size, @values.size].max
|
224
|
+
|
225
|
+
prune if key_map_size - key_value_size > cutoff
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) Holger Just
|
4
|
+
#
|
5
|
+
# This software may be modified and distributed under the terms
|
6
|
+
# of the MIT license. See the LICENSE.txt file for details.
|
7
|
+
|
8
|
+
require "weak/map/deletable"
|
9
|
+
|
10
|
+
##
|
11
|
+
module Weak
|
12
|
+
class Map
|
13
|
+
# This {Weak::Map} strategy targets Ruby < 3.3.0.
|
14
|
+
#
|
15
|
+
# Its `ObjectSpace::WeakMap` uses weak keys and weak values so that either
|
16
|
+
# the key or the value can be independently garbage collected. If either of
|
17
|
+
# them vanishes, the entry is removed.
|
18
|
+
#
|
19
|
+
# The `ObjectSpace::WeakMap` does not allow to explicitly delete entries.
|
20
|
+
# We emulate this by setting the garbage-collectible value of a deleted
|
21
|
+
# entry to a simple new object. This value will be garbage collected on the
|
22
|
+
# next GC run which will then remove the entry. When accessing elements, we
|
23
|
+
# delete and filter out these recently deleted entries.
|
24
|
+
module WeakKeys
|
25
|
+
include Deletable
|
26
|
+
|
27
|
+
# Checks if this strategy is usable for the current Ruby version.
|
28
|
+
#
|
29
|
+
# @return [Bool] truethy for Ruby (aka. MRI, aka. YARV), falsey otherwise
|
30
|
+
def self.usable?
|
31
|
+
RUBY_ENGINE == "ruby"
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!macro weak_map_accessor_read
|
35
|
+
def [](key)
|
36
|
+
raw_value = @map[key]
|
37
|
+
missing?(raw_value) ? _default(key) : value!(raw_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!macro weak_map_accessor_write
|
41
|
+
def []=(key, value)
|
42
|
+
@map[key] = value.nil? ? NIL : value
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!macro weak_map_method_clear
|
47
|
+
def clear
|
48
|
+
@map = ObjectSpace::WeakMap.new
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!macro weak_map_method_delete
|
53
|
+
def delete(key)
|
54
|
+
raw_value = @map[key]
|
55
|
+
if have?(raw_value)
|
56
|
+
@map[key] = DeletedEntry.new
|
57
|
+
value!(raw_value)
|
58
|
+
elsif block_given?
|
59
|
+
yield(key)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @!macro weak_map_method_each_key
|
64
|
+
def each_key
|
65
|
+
return enum_for(__method__) { size } unless block_given?
|
66
|
+
|
67
|
+
@map.keys.each do |key|
|
68
|
+
yield key unless missing?(@map[key])
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# @!macro weak_map_method_each_pair
|
74
|
+
def each_pair
|
75
|
+
return enum_for(__method__) { size } unless block_given?
|
76
|
+
|
77
|
+
@map.keys.each do |key|
|
78
|
+
raw_value = @map[key]
|
79
|
+
yield [key, value!(raw_value)] unless missing?(raw_value)
|
80
|
+
end
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!macro weak_map_method_each_value
|
85
|
+
def each_value
|
86
|
+
return enum_for(__method__) { size } unless block_given?
|
87
|
+
|
88
|
+
@map.values.each do |raw_value|
|
89
|
+
yield value!(raw_value) unless missing?(raw_value)
|
90
|
+
end
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!macro weak_map_method_fetch
|
95
|
+
def fetch(key, default = UNDEFINED, &block)
|
96
|
+
raw_value = @map[key]
|
97
|
+
if have?(raw_value)
|
98
|
+
value!(raw_value)
|
99
|
+
else
|
100
|
+
_fetch_default(key, default, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# @!macro weak_map_method_include_question
|
105
|
+
def include?(key)
|
106
|
+
have?(@map[key])
|
107
|
+
end
|
108
|
+
|
109
|
+
# @!macro weak_map_method_keys
|
110
|
+
def keys
|
111
|
+
@map.keys.delete_if { |key| missing?(@map[key]) }
|
112
|
+
end
|
113
|
+
|
114
|
+
# @!macro weak_map_method_prune
|
115
|
+
def prune
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
# @!macro weak_map_method_size
|
120
|
+
def size
|
121
|
+
each_key.count
|
122
|
+
end
|
123
|
+
|
124
|
+
# @!macro weak_map_method_values
|
125
|
+
def values
|
126
|
+
values = []
|
127
|
+
@map.values.each do |raw_value|
|
128
|
+
values << value!(raw_value) unless missing?(raw_value)
|
129
|
+
end
|
130
|
+
values
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) Holger Just
|
4
|
+
#
|
5
|
+
# This software may be modified and distributed under the terms
|
6
|
+
# of the MIT license. See the LICENSE.txt file for details.
|
7
|
+
|
8
|
+
##
|
9
|
+
module Weak
|
10
|
+
class Map
|
11
|
+
# This {Weak::Map} strategy targets Ruby >= 3.3.0.
|
12
|
+
# Older Ruby versions require additional indirections implemented in
|
13
|
+
# {Weak::Map::WeakKeys}:
|
14
|
+
#
|
15
|
+
# - https://bugs.ruby-lang.org/issues/19561
|
16
|
+
#
|
17
|
+
# Ruby's `ObjectSpace::WeakMap` uses weak keys and weak values so that
|
18
|
+
# either the key or the value can be independently garbage collected. If
|
19
|
+
# either of them vanishes, the entry is removed.
|
20
|
+
#
|
21
|
+
# The `ObjectSpace::WeakMap` also allows to delete entries. This allows us
|
22
|
+
# to directly use the `ObjectSpace::WeakMap` as a storage the same way a
|
23
|
+
# `Set` uses a `Hash` object object as storage.
|
24
|
+
module WeakKeysWithDelete
|
25
|
+
# Checks if this strategy is usable for the current Ruby version.
|
26
|
+
#
|
27
|
+
# @return [Bool] truethy for Ruby (aka. MRI, aka. YARV) >= 3.3.0,
|
28
|
+
# falsey otherwise
|
29
|
+
def self.usable?
|
30
|
+
RUBY_ENGINE == "ruby" &&
|
31
|
+
ObjectSpace::WeakMap.instance_methods.include?(:delete)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!macro weak_map_accessor_read
|
35
|
+
def [](key)
|
36
|
+
value = @map[key]
|
37
|
+
value = _default(key) if value.nil? && !@map.key?(key)
|
38
|
+
value
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!macro weak_map_accessor_write
|
42
|
+
def []=(key, value)
|
43
|
+
@map[key] = value
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!macro weak_map_method_clear
|
48
|
+
def clear
|
49
|
+
@map = ObjectSpace::WeakMap.new
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!macro weak_map_method_delete
|
54
|
+
def delete(key, &block)
|
55
|
+
@map.delete(key, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @!macro weak_map_method_each_key
|
59
|
+
def each_key
|
60
|
+
return enum_for(__method__) { size } unless block_given?
|
61
|
+
|
62
|
+
@map.keys.each do |key|
|
63
|
+
yield(key)
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# @!macro weak_map_method_each_pair
|
70
|
+
def each_pair(&block)
|
71
|
+
return enum_for(__method__) { size } unless block_given?
|
72
|
+
|
73
|
+
array = []
|
74
|
+
@map.each do |key, value|
|
75
|
+
array << key << value
|
76
|
+
end
|
77
|
+
array.each_slice(2, &block)
|
78
|
+
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!macro weak_map_method_each_value
|
83
|
+
def each_value
|
84
|
+
return enum_for(__method__) { size } unless block_given?
|
85
|
+
|
86
|
+
@map.values.each do |value|
|
87
|
+
yield(value)
|
88
|
+
end
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
# @!macro weak_map_method_fetch
|
94
|
+
def fetch(key, default = UNDEFINED, &block)
|
95
|
+
value = @map[key]
|
96
|
+
value = _fetch_default(key, default, &block) if value.nil? && !@map.key?(key)
|
97
|
+
value
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!macro weak_map_method_include_question
|
101
|
+
def include?(key)
|
102
|
+
@map.key?(key)
|
103
|
+
end
|
104
|
+
|
105
|
+
# @!macro weak_map_method_keys
|
106
|
+
def keys
|
107
|
+
@map.keys
|
108
|
+
end
|
109
|
+
|
110
|
+
# @!macro weak_map_method_prune
|
111
|
+
def prune
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
# @!macro weak_map_method_size
|
116
|
+
def size
|
117
|
+
@map.size
|
118
|
+
end
|
119
|
+
|
120
|
+
# @!macro weak_map_method_values
|
121
|
+
def values
|
122
|
+
@map.values
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|