farce 0.5.0-aarch64-linux-gnu → 0.6.0-aarch64-linux-gnu
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/docs/modes.md +1 -1
- data/docs/utilities.md +1 -0
- data/ext/farce/atom.c +23 -7
- data/ext/farce/containers.h +3 -0
- data/ext/farce/counter.c +4 -6
- data/ext/farce/depend +3 -1
- data/ext/farce/extconf.rb +19 -14
- data/ext/farce/farce.c +101 -3
- data/ext/farce/map.c +1 -4
- data/ext/farce/trie.c +140 -72
- data/ext/farce/trie_search.h +114 -0
- data/ext/farce/vector.c +50 -18
- data/ext/farce/weak_map.c +4 -8
- data/ext/farce/weak_references.h +23 -0
- data/ext/java/org/farce/FiberScheduler.java +8 -6
- data/lib/farce/abstract/atom.rb +5 -1
- data/lib/farce/abstract/counter.rb +5 -4
- data/lib/farce/abstract/molecule.rb +14 -14
- data/lib/farce/abstract/vector.rb +1 -1
- data/lib/farce/atom.rb +6 -0
- data/lib/farce/clock.rb +8 -7
- data/lib/farce/counter.rb +6 -0
- data/lib/farce/deduper.rb +12 -4
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jvm/counter.rb +0 -2
- data/lib/farce/engine/ruby/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/shared/weak_cache_mirror.rb +56 -0
- data/lib/farce/engine/ruby.rb +5 -1
- data/lib/farce/engine/shared/trie/ignore_case.rb +136 -0
- data/lib/farce/engine/shared/trie.rb +14 -1
- data/lib/farce/engine/shared/trie_builder.rb +19 -8
- data/lib/farce/engine/shared/unshared_vector.rb +3 -1
- data/lib/farce/engine/shared/vector.rb +2 -0
- data/lib/farce/engine/shared.rb +2 -0
- data/lib/farce/engine/truffleruby/native/counter.rb +0 -2
- data/lib/farce/engine/truffleruby.rb +9 -0
- data/lib/farce/extension.rb +5 -3
- data/lib/farce/integrations/sorted_set.rb +1 -0
- data/lib/farce/internal/fake_ractor.rb +1 -1
- data/lib/farce/internal/freeze.rb +1 -5
- data/lib/farce/internal/map_value_modes.rb +8 -7
- data/lib/farce/internal.rb +11 -0
- data/lib/farce/local/counter.rb +4 -10
- data/lib/farce/map.rb +8 -1
- data/lib/farce/memoize.rb +10 -5
- data/lib/farce/shareable.rb +3 -0
- data/lib/farce/strict/weak_cache.rb +199 -0
- data/lib/farce/vector.rb +8 -7
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +18 -0
- data/lib/farce/walker/modification.rb +28 -14
- data/lib/farce/walker.rb +9 -1
- data/lib/farce/weak_key_map.rb +1 -1
- data/lib/farce/weak_map.rb +1 -1
- data/lib/farce/weak_value_map.rb +1 -1
- metadata +6 -1
data/lib/farce/local/counter.rb
CHANGED
|
@@ -50,19 +50,13 @@ module Farce
|
|
|
50
50
|
|
|
51
51
|
# Increment the counter atomically.
|
|
52
52
|
# @param by [Numeric, String, #to_int] the amount, converted to an Integer
|
|
53
|
-
# @return [
|
|
54
|
-
def increment(by = 1)
|
|
55
|
-
internal_counter.increment(by)
|
|
56
|
-
self
|
|
57
|
-
end
|
|
53
|
+
# @return [Integer] the value produced by this atomic update
|
|
54
|
+
def increment(by = 1) = internal_counter.increment(by)
|
|
58
55
|
|
|
59
56
|
# Decrement the counter atomically.
|
|
60
57
|
# @param by [Numeric, String, #to_int] the amount, converted to an Integer
|
|
61
|
-
# @return [
|
|
62
|
-
def decrement(by = 1)
|
|
63
|
-
internal_counter.decrement(by)
|
|
64
|
-
self
|
|
65
|
-
end
|
|
58
|
+
# @return [Integer] the value produced by this atomic update
|
|
59
|
+
def decrement(by = 1) = internal_counter.decrement(by)
|
|
66
60
|
|
|
67
61
|
alias subtract decrement
|
|
68
62
|
alias remove decrement
|
data/lib/farce/map.rb
CHANGED
|
@@ -48,7 +48,14 @@ module Farce
|
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
|
-
def new_map(
|
|
51
|
+
def new_map(
|
|
52
|
+
compare_by_identity: false,
|
|
53
|
+
compare_keys_by_identity: compare_by_identity,
|
|
54
|
+
compare_values_by_identity: compare_by_identity
|
|
55
|
+
)
|
|
56
|
+
Internal::StrictMap.new(compare_by_identity:, compare_keys_by_identity:, compare_values_by_identity:)
|
|
57
|
+
end
|
|
58
|
+
|
|
52
59
|
def freeze_backend = @map
|
|
53
60
|
|
|
54
61
|
Internal.prepare_map_access(self, :modes)
|
data/lib/farce/memoize.rb
CHANGED
|
@@ -77,7 +77,8 @@ module Farce
|
|
|
77
77
|
#
|
|
78
78
|
# @param mode [Symbol]
|
|
79
79
|
# The mode of the memoization. Ignored for any scope other than `:global`.
|
|
80
|
-
#
|
|
80
|
+
# Use `:strict` to cache shareable results directly, including with `weak: true`.
|
|
81
|
+
# Defaults to `:raise` with `weak: true`, which also supports `:make_shareable` and `:dedup`.
|
|
81
82
|
#
|
|
82
83
|
# @param weak [Boolean]
|
|
83
84
|
# Whether to use weak memoization.
|
|
@@ -159,9 +160,13 @@ module Farce
|
|
|
159
160
|
|
|
160
161
|
case scope
|
|
161
162
|
when :global
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
if mode == :strict
|
|
164
|
+
init = "::Farce::Strict::#{atom_class}.new"
|
|
165
|
+
else
|
|
166
|
+
modes = weak ? Internal::WeakModeManager::MODES : MODES
|
|
167
|
+
raise ArgumentError, "unsupported mode: #{mode.inspect}" unless modes.include?(mode)
|
|
168
|
+
init = "::Farce::#{atom_class}.new(mode: #{mode.inspect})"
|
|
169
|
+
end
|
|
165
170
|
args = methods.join(", ")
|
|
166
171
|
args += ", mode: #{mode.inspect}" unless mode == :smart
|
|
167
172
|
when :unshared
|
|
@@ -187,7 +192,7 @@ module Farce
|
|
|
187
192
|
initializer << " #{ivar} = #{init}\n"
|
|
188
193
|
module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
189
194
|
#{visibility} def #{method}
|
|
190
|
-
value = #{ivar}.store_if_absent do
|
|
195
|
+
value = #{ivar}.value || #{ivar}.store_if_absent do
|
|
191
196
|
result = super()
|
|
192
197
|
nil.equal?(result) ? NIL_VALUE : result
|
|
193
198
|
end
|
data/lib/farce/shareable.rb
CHANGED
|
@@ -13,6 +13,9 @@ module Farce
|
|
|
13
13
|
# object = MyClass.new
|
|
14
14
|
# Ractor.shareable?(object) # => true
|
|
15
15
|
module Shareable
|
|
16
|
+
# @api private
|
|
17
|
+
def self.included(base) = Internal.prepare_shareable_class(base)
|
|
18
|
+
|
|
16
19
|
# A structurally frozen wrapper whose logical frozen state is owned by its backend.
|
|
17
20
|
# The including class must implement a private `freeze_backend` method.
|
|
18
21
|
#
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Strict
|
|
7
|
+
# A weak cache that allows reusing previously generated values without holding strong references to them.
|
|
8
|
+
# Only shareable keys and values can be used.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# cache = Farce::Strict::WeakCache.new
|
|
12
|
+
# value = cache.store_if_absent(:key) { ExpensiveClass.new.freeze }
|
|
13
|
+
class WeakCache
|
|
14
|
+
include Shareable::Delegated
|
|
15
|
+
include Internal::Inspect
|
|
16
|
+
|
|
17
|
+
# Creates a new WeakCache instance if it outperforms {Strict::WeakValueMap} on the current platform.
|
|
18
|
+
# Otherwise it creates a standard WeakValueMap instance.
|
|
19
|
+
# @return [WeakCache, Strict::WeakValueMap] The created cache instance.
|
|
20
|
+
def self.create(...) = Internal.native_weak_maps? ? Strict::WeakValueMap.new(...) : WeakCache.new(...)
|
|
21
|
+
|
|
22
|
+
# @param initial_mapping [Hash, Farce::Abstract::Map, #each, nil]
|
|
23
|
+
# The entries to store initially. Each iteration must yield a key-value pair.
|
|
24
|
+
# Nil creates an empty map.
|
|
25
|
+
# @param compare_by_identity [Boolean]
|
|
26
|
+
# Whether keys use object identity instead of `hash` and `eql?`.
|
|
27
|
+
def initialize(initial_mapping = nil, compare_by_identity: false)
|
|
28
|
+
@map = Strict::WeakValueMap.new(initial_mapping, compare_keys_by_identity: compare_by_identity)
|
|
29
|
+
initialize_cache
|
|
30
|
+
super()
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @api private
|
|
34
|
+
def marshal_dump = [1, @map]
|
|
35
|
+
|
|
36
|
+
# @api private
|
|
37
|
+
def marshal_load(data)
|
|
38
|
+
@map, = Internal::MarshalSupport.payload(data, 1)
|
|
39
|
+
initialize_cache
|
|
40
|
+
publish_shareable
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Retrieves or stores a value for the given key.
|
|
44
|
+
# The block is called to generate the value if the key is absent.
|
|
45
|
+
# Concurrent calls for the same key reuse the generated value.
|
|
46
|
+
# Once that value is collected, a later call generates it again.
|
|
47
|
+
#
|
|
48
|
+
# @param key [BasicObject] The key to look up.
|
|
49
|
+
# @yield Called without arguments when the key is absent.
|
|
50
|
+
# @yieldreturn [BasicObject] The value to store and return.
|
|
51
|
+
# @return [BasicObject] The associated value.
|
|
52
|
+
def store_if_absent(key, &)
|
|
53
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
54
|
+
@cache.store_if_absent(key, &)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
alias compute_if_absent store_if_absent
|
|
58
|
+
|
|
59
|
+
# Store a value only when the key is absent.
|
|
60
|
+
# Existing nil and false values count as present.
|
|
61
|
+
# @param key [BasicObject] The key to look up or store.
|
|
62
|
+
# @param value [BasicObject] The value to store if the key is absent.
|
|
63
|
+
# @return [BasicObject, nil] The existing value, or nil when a value was inserted.
|
|
64
|
+
def put_if_absent(key, value)
|
|
65
|
+
inserted = false
|
|
66
|
+
previous = store_if_absent(key) do
|
|
67
|
+
inserted = true
|
|
68
|
+
value
|
|
69
|
+
end
|
|
70
|
+
inserted ? nil : previous
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Look up a value without generating it.
|
|
74
|
+
# @param key [BasicObject] The key to look up.
|
|
75
|
+
# @return [BasicObject, nil] The associated value, or nil if the key is absent.
|
|
76
|
+
def [](key) = @cache.fetch(key) { nil } # rubocop:disable Style/RedundantFetchBlock
|
|
77
|
+
|
|
78
|
+
# Return a lookup key and its associated value if present.
|
|
79
|
+
# @param key [BasicObject] The key to look up.
|
|
80
|
+
# @return [Array(BasicObject, BasicObject), nil] The pair `[key, value]`, or nil if the key is absent.
|
|
81
|
+
def assoc(key)
|
|
82
|
+
value = fetch(key) { return nil }
|
|
83
|
+
[key, value]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Return whether a live entry exists, including entries with nil or false values.
|
|
87
|
+
# @param key [BasicObject] The key to look up.
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
def key?(key)
|
|
90
|
+
@cache.fetch(key) { return false }
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
alias has_key? key?
|
|
95
|
+
alias include? key?
|
|
96
|
+
alias member? key?
|
|
97
|
+
|
|
98
|
+
# Look up a value in nested maps or arrays.
|
|
99
|
+
# @param key [BasicObject] The key to look up.
|
|
100
|
+
# @param rest [Array<BasicObject>] Additional keys or indices for the nested value.
|
|
101
|
+
# @return [BasicObject, nil] The nested value, or nil if an intermediate value is absent or nil.
|
|
102
|
+
def dig(key, *rest)
|
|
103
|
+
value = self[key]
|
|
104
|
+
return value if rest.empty? || value.nil?
|
|
105
|
+
value.dig(*rest)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Return the stored key matching a lookup key.
|
|
109
|
+
# @param key [BasicObject] The key to look up.
|
|
110
|
+
# @return [BasicObject, nil] The stored key, or nil if the key is absent.
|
|
111
|
+
def getkey(key) = @map.getkey(key)
|
|
112
|
+
|
|
113
|
+
# Fetch the value associated with a key, using the same missing-key behavior as Hash#fetch.
|
|
114
|
+
# If both a default and a block are provided, the block takes precedence.
|
|
115
|
+
#
|
|
116
|
+
# @overload fetch(key)
|
|
117
|
+
# @param key [BasicObject] The key to look up.
|
|
118
|
+
# @return [BasicObject] The associated value.
|
|
119
|
+
# @raise [KeyError] If the key is absent.
|
|
120
|
+
#
|
|
121
|
+
# @overload fetch(key, default)
|
|
122
|
+
# @param key [BasicObject] The key to look up.
|
|
123
|
+
# @param default [BasicObject] The value to return if the key is absent.
|
|
124
|
+
# @return [BasicObject] The associated value or `default`.
|
|
125
|
+
#
|
|
126
|
+
# @overload fetch(key)
|
|
127
|
+
# @param key [BasicObject] The key to look up.
|
|
128
|
+
# @yield [key] Called if the key is absent.
|
|
129
|
+
# @yieldparam key [BasicObject] The missing key.
|
|
130
|
+
# @yieldreturn [BasicObject] The value to return.
|
|
131
|
+
# @return [BasicObject] The associated value or the block result.
|
|
132
|
+
def fetch(*arguments)
|
|
133
|
+
unless arguments.length.between?(1, 2)
|
|
134
|
+
raise ArgumentError, "wrong number of arguments (given #{arguments.length}, expected 1..2)"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
key, default = arguments
|
|
138
|
+
warn "block supersedes default value argument", uplevel: 1 if block_given? && arguments.length == 2
|
|
139
|
+
@cache.fetch(key) do
|
|
140
|
+
return yield(key) if block_given?
|
|
141
|
+
return default if arguments.length == 2
|
|
142
|
+
raise KeyError.new("key not found: #{key.inspect}", receiver: self, key: key)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Fetch values in the requested order, using {#fetch} for missing keys.
|
|
147
|
+
# @param keys [Array<BasicObject>] The keys to look up.
|
|
148
|
+
# @yield [key] Called for each missing key.
|
|
149
|
+
# @yieldparam key [BasicObject] The missing key.
|
|
150
|
+
# @yieldreturn [BasicObject] The fallback value, which is not stored.
|
|
151
|
+
# @return [Array<BasicObject>] The associated values or block results.
|
|
152
|
+
def fetch_values(*keys, &) = keys.map { fetch(it, &) }
|
|
153
|
+
|
|
154
|
+
# Look up values in the requested order, returning nil for missing keys.
|
|
155
|
+
# @param keys [Array<BasicObject>] The keys to look up.
|
|
156
|
+
# @return [Array<BasicObject>] The associated values, with nil for missing keys.
|
|
157
|
+
def values_at(*keys) = keys.map { self[it] }
|
|
158
|
+
|
|
159
|
+
# @return [Boolean] Whether keys are compared by identity instead of `hash` and `eql?`.
|
|
160
|
+
def compare_keys_by_identity? = @map.compare_keys_by_identity?
|
|
161
|
+
|
|
162
|
+
# (see #compare_keys_by_identity?)
|
|
163
|
+
def compare_by_identity? = compare_keys_by_identity?
|
|
164
|
+
|
|
165
|
+
# @return [Boolean] Whether keys must be Ractor-shareable, including copied mutable strings.
|
|
166
|
+
def shareable_keys? = true
|
|
167
|
+
|
|
168
|
+
# @return [Boolean] Whether values must be Ractor-shareable.
|
|
169
|
+
def shareable_values? = true
|
|
170
|
+
|
|
171
|
+
# @return [Boolean] Whether keys are referenced weakly.
|
|
172
|
+
def weak_keys? = false
|
|
173
|
+
|
|
174
|
+
# @return [Boolean] Whether values are referenced weakly.
|
|
175
|
+
def weak_values? = true
|
|
176
|
+
|
|
177
|
+
# @api private
|
|
178
|
+
def inspect_with(inspector)
|
|
179
|
+
super do
|
|
180
|
+
yield if block_given?
|
|
181
|
+
inspector.breakable
|
|
182
|
+
inspector.group("{", "}") do
|
|
183
|
+
inspector.breakable ""
|
|
184
|
+
inspector.seplist(@map, nil, :each) do |key, value|
|
|
185
|
+
inspector.group { inspector.hash_pair(key, value) }
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
alias to_s inspect
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def initialize_cache = @cache = Internal.native_weak_maps? ? @map : Internal::WeakCacheMirror.new(@map)
|
|
196
|
+
def freeze_backend = @map
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
data/lib/farce/vector.rb
CHANGED
|
@@ -26,14 +26,15 @@ module Farce
|
|
|
26
26
|
# @raise [ArgumentError] If the size is negative.
|
|
27
27
|
def initialize(source = nil, mode: :smart, compare_by_identity: false)
|
|
28
28
|
source = normalize_vector_source(source)
|
|
29
|
-
raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
|
|
30
|
-
unless true.equal?(compare_by_identity) || false.equal?(compare_by_identity)
|
|
31
|
-
raise ArgumentError, "compare_by_identity must be true or false"
|
|
32
|
-
end
|
|
29
|
+
raise TypeError, "source must be an Array" unless source.nil? || Integer === source || source.is_a?(Array)
|
|
33
30
|
|
|
34
|
-
@compare_by_identity = compare_by_identity
|
|
31
|
+
@compare_by_identity = compare_by_identity ? true : false
|
|
35
32
|
@manager = ModeManager.new(mode:)
|
|
36
|
-
|
|
33
|
+
if source && !(Integer === source)
|
|
34
|
+
source = source.map { @manager.wrap(it) }
|
|
35
|
+
raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
|
|
36
|
+
end
|
|
37
|
+
@vector = Internal::Vector.new_for_modes(source)
|
|
37
38
|
super()
|
|
38
39
|
end
|
|
39
40
|
|
|
@@ -53,7 +54,7 @@ module Farce
|
|
|
53
54
|
def marshal_load(data)
|
|
54
55
|
entries, @manager, identity, frozen = Internal::MarshalSupport.payload(data, 4)
|
|
55
56
|
@compare_by_identity = identity
|
|
56
|
-
@vector = Internal::Vector.
|
|
57
|
+
@vector = Internal::Vector.new_for_modes(nil)
|
|
57
58
|
publish_shareable
|
|
58
59
|
entries.each { @vector.push(Internal::MarshalSupport.restore_value(it)) }
|
|
59
60
|
Internal::MarshalSupport.freeze(self, frozen)
|
data/lib/farce/version.rb
CHANGED
|
@@ -190,6 +190,24 @@ module Farce
|
|
|
190
190
|
super(object, walker)
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
+
define(Struct) do |object, walker|
|
|
194
|
+
result = walker.update(object, object.to_a) do |target, results|
|
|
195
|
+
results.each_with_index { |value, index| target[index] = value unless target[index].equal?(value) }
|
|
196
|
+
target
|
|
197
|
+
end
|
|
198
|
+
super(result, walker)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
define(Range) do |object, walker|
|
|
202
|
+
names = object.instance_variables
|
|
203
|
+
values = [object.begin, object.end, *names.map { object.instance_variable_get(it) }]
|
|
204
|
+
walker.update(object, values) do |target, results|
|
|
205
|
+
names.each_with_index { |name, index| target.instance_variable_set(name, results[index + 2]) }
|
|
206
|
+
Internal.initialize_range(target, results[0], results[1], object.exclude_end?)
|
|
207
|
+
target
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
193
211
|
define(Data) do |object, walker|
|
|
194
212
|
walker.rebuild_data(object)
|
|
195
213
|
end
|
|
@@ -172,14 +172,14 @@ module Farce
|
|
|
172
172
|
node ? node.changed == true : (@seen.key?(object) && !@seen[object].equal?(object))
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
# Follow pending references to check that hashing cannot reach unfinished
|
|
175
|
+
# Follow pending references to check that hashing cannot reach unfinished values.
|
|
176
176
|
def hashable?(object)
|
|
177
|
-
return true unless @
|
|
177
|
+
return true unless @unfinished_values && !@unfinished_values.empty?
|
|
178
178
|
!Walker.visit(object, constants:, class_variables:) do |value, traversal|
|
|
179
|
-
traversal.return(true) if @
|
|
179
|
+
traversal.return(true) if @unfinished_values.key?(value)
|
|
180
180
|
node = @nodes[value] || @destinations&.[](value)
|
|
181
181
|
if node && node.plans
|
|
182
|
-
traversal.return(true) if @
|
|
182
|
+
traversal.return(true) if @unfinished_values.key?(node.destination)
|
|
183
183
|
node.plans.each { |plan| plan.children.each { traversal.visit(resolved(it)) } }
|
|
184
184
|
else
|
|
185
185
|
traversal.traverse
|
|
@@ -188,10 +188,10 @@ module Farce
|
|
|
188
188
|
end
|
|
189
189
|
end
|
|
190
190
|
|
|
191
|
-
# Reject keys and set elements whose
|
|
191
|
+
# Reject keys and set elements whose immutable dependencies are still being built.
|
|
192
192
|
def check_hash_key(object)
|
|
193
193
|
return object if hashable?(object)
|
|
194
|
-
raise ArgumentError, "hash key or set element refers to an unfinished Data value"
|
|
194
|
+
raise ArgumentError, "hash key or set element refers to an unfinished Data or Range value"
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
private
|
|
@@ -217,19 +217,19 @@ module Farce
|
|
|
217
217
|
dirty ||= plan.hash_keys && (index % plan.key_stride).zero? && child && child.changed
|
|
218
218
|
end
|
|
219
219
|
node.changed ||= dirty
|
|
220
|
-
plan.dirty = dirty
|
|
220
|
+
plan.dirty = dirty || (Range === node.destination && @unfinished_values&.key?(node.destination))
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
-
# Choose a writable destination, copying lazily or allocating unfinished
|
|
223
|
+
# Choose a writable destination, copying lazily or allocating unfinished values.
|
|
224
224
|
def prepare(node, rebuild: false)
|
|
225
225
|
previous = node.destination
|
|
226
226
|
return previous unless rebuild || previous.equal?(node.original)
|
|
227
227
|
object = node.original
|
|
228
228
|
return object if Internal::Noncopyable === object || Abstract::LeaseMap === object
|
|
229
229
|
destination =
|
|
230
|
-
if Data === object
|
|
230
|
+
if Data === object || Range === object
|
|
231
231
|
value = object.class.allocate
|
|
232
|
-
(@
|
|
232
|
+
(@unfinished_values ||= {}.compare_by_identity)[value] = true
|
|
233
233
|
value
|
|
234
234
|
elsif @modify == true
|
|
235
235
|
send_to(object, :frozen?) ? send_to(object, :clone, freeze: false) : object
|
|
@@ -241,6 +241,7 @@ module Farce
|
|
|
241
241
|
(@destinations ||= {}.compare_by_identity)[destination] = node unless destination.equal?(object)
|
|
242
242
|
node.destination = destination
|
|
243
243
|
node.result = destination if !node.skipped && (node.result.equal?(object) || node.result.equal?(previous))
|
|
244
|
+
node.plans&.each { it.dirty = true } if Range === destination
|
|
244
245
|
destination
|
|
245
246
|
end
|
|
246
247
|
|
|
@@ -259,7 +260,7 @@ module Farce
|
|
|
259
260
|
node.destination = result
|
|
260
261
|
node.result = result if node.result.equal?(old)
|
|
261
262
|
end
|
|
262
|
-
@
|
|
263
|
+
@unfinished_values&.delete(node.destination) if Data === node.destination || Range === node.destination
|
|
263
264
|
return unless node.cyclic
|
|
264
265
|
plan.target = node.destination
|
|
265
266
|
plan.applied = plan.results.dup
|
|
@@ -281,6 +282,9 @@ module Farce
|
|
|
281
282
|
if freeze
|
|
282
283
|
node.changed = true unless send_to(node.result, :frozen?)
|
|
283
284
|
prepare(node) if @modify != true && node.result.equal?(node.original) && !send_to(node.result, :frozen?)
|
|
285
|
+
if Range === node.destination && @unfinished_values&.key?(node.destination)
|
|
286
|
+
node.plans&.each { apply(node, it) }
|
|
287
|
+
end
|
|
284
288
|
send_to(node.result, :freeze)
|
|
285
289
|
end
|
|
286
290
|
finalize_node(node) if finalize
|
|
@@ -308,15 +312,25 @@ module Farce
|
|
|
308
312
|
freezing = !node.skipped && (node.freeze_result || @freeze == true)
|
|
309
313
|
needed = node.plans&.any?(&:dirty) || (freezing && @modify != true && !send_to(node.original, :frozen?))
|
|
310
314
|
prepare(node) if needed
|
|
311
|
-
next unless Data === node.destination
|
|
312
|
-
|
|
315
|
+
next unless Data === node.destination || Range === node.destination
|
|
316
|
+
# Rebuild only after this destination's own assignments become stale.
|
|
317
|
+
next unless node.plans&.any? do |plan|
|
|
318
|
+
plan.target.equal?(node.destination) && plan.applied &&
|
|
319
|
+
plan.results.each_with_index.any? { |value, index| !value.equal?(plan.applied[index]) }
|
|
313
320
|
end
|
|
314
321
|
prepare(node, rebuild: true)
|
|
315
322
|
end
|
|
316
323
|
break if component.each_with_index.all? { |node, index| node.destination.equal?(destinations[index]) }
|
|
317
324
|
end
|
|
318
325
|
|
|
319
|
-
component.sort_by
|
|
326
|
+
ordered = component.sort_by do |node|
|
|
327
|
+
if Data === node.destination || Range === node.destination
|
|
328
|
+
2
|
|
329
|
+
else
|
|
330
|
+
node.plans&.any?(&:hash_keys) ? 1 : 0
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
ordered.each do |node|
|
|
320
334
|
@node = node
|
|
321
335
|
@current_object = node.original
|
|
322
336
|
node.plans&.each do |plan|
|
data/lib/farce/walker.rb
CHANGED
|
@@ -140,14 +140,18 @@ module Farce
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
# Transform a graph using the callback's return values.
|
|
143
|
+
#
|
|
143
144
|
# Call {#traverse} to transform an object's children. Returning another value
|
|
144
145
|
# replaces the object without automatically visiting that replacement.
|
|
145
146
|
# Unchanged branches retain their identity, including when copy is enabled.
|
|
147
|
+
#
|
|
146
148
|
# Changed cycles are connected before results are frozen or finalized.
|
|
147
149
|
# A cyclic callback may run again as child replacements become known. Keep
|
|
148
150
|
# callbacks repeatable and put publication or caching in {#finalize}.
|
|
149
151
|
# Use {#freeze_result} instead of freezing a preliminary traversal result.
|
|
152
|
+
#
|
|
150
153
|
# Data values are rebuilt only when members change, using their normal initializer.
|
|
154
|
+
# Ranges are rebuilt when endpoints or instance variables change.
|
|
151
155
|
# Noncopyable coordination objects, such as leases, are updated in place.
|
|
152
156
|
# Constants and class variables are assigned only when their value
|
|
153
157
|
# changes identity. Constant replacement can emit Ruby redefinition warnings.
|
|
@@ -156,6 +160,7 @@ module Farce
|
|
|
156
160
|
#
|
|
157
161
|
# A walk is not an atomic snapshot or update. Coordinate concurrent writers
|
|
158
162
|
# when transforming container structure, especially map keys.
|
|
163
|
+
#
|
|
159
164
|
# @param object [BasicObject] the root object
|
|
160
165
|
# @!macro walker_module_options
|
|
161
166
|
# @param copy [Boolean, Symbol] false to edit mutable objects in place, true to
|
|
@@ -171,7 +176,7 @@ module Farce
|
|
|
171
176
|
# @raise [ArgumentError] if copy is neither a boolean nor a Symbol
|
|
172
177
|
# @raise [ArgumentError] if cyclic callbacks do not converge within 32 passes,
|
|
173
178
|
# or a cyclic finalizer replaces its result
|
|
174
|
-
# @raise [ArgumentError] if a hash key or set element refers to a Data value
|
|
179
|
+
# @raise [ArgumentError] if a hash key or set element refers to a Data or Range value
|
|
175
180
|
# still being constructed. Identity-based containers do not hash their keys.
|
|
176
181
|
def self.modify(object, copy: false, freeze: nil, constants: true, class_variables: true, &callback)
|
|
177
182
|
raise LocalJumpError, "no block given" unless callback
|
|
@@ -263,9 +268,11 @@ module Farce
|
|
|
263
268
|
def remember(object, result) = @seen[object] = result
|
|
264
269
|
|
|
265
270
|
# Visit child values and describe how to assign their replacements.
|
|
271
|
+
#
|
|
266
272
|
# The assignment block only runs during modification and may run again for cycles.
|
|
267
273
|
# It receives a writable target and the resolved children. Do not mutate object
|
|
268
274
|
# outside this block. Return the result of update from a traversal definition.
|
|
275
|
+
#
|
|
269
276
|
# @param object [BasicObject] the object being traversed
|
|
270
277
|
# @param values [Array] child references in assignment order
|
|
271
278
|
# @param hash_keys [Boolean] rebuild when key descendants change in place
|
|
@@ -288,6 +295,7 @@ module Farce
|
|
|
288
295
|
# and requested freezing. Registering another finalizer replaces the first.
|
|
289
296
|
# Cyclic finalizers must preserve identity. Acyclic finalizers may return a
|
|
290
297
|
# canonical replacement, which is propagated to the parent.
|
|
298
|
+
#
|
|
291
299
|
# @yieldparam result [BasicObject] the settled result
|
|
292
300
|
# @yieldparam cyclic [Boolean] whether the node belongs to a cycle
|
|
293
301
|
# @yieldreturn [BasicObject] the final result
|
data/lib/farce/weak_key_map.rb
CHANGED
data/lib/farce/weak_map.rb
CHANGED
data/lib/farce/weak_value_map.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: farce
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: aarch64-linux-gnu
|
|
6
6
|
authors:
|
|
7
7
|
- Konstantin Haase
|
|
@@ -72,6 +72,7 @@ files:
|
|
|
72
72
|
- ext/farce/transaction.h
|
|
73
73
|
- ext/farce/tree_map.c
|
|
74
74
|
- ext/farce/trie.c
|
|
75
|
+
- ext/farce/trie_search.h
|
|
75
76
|
- ext/farce/unshareable.c
|
|
76
77
|
- ext/farce/unshared_io_pool.h
|
|
77
78
|
- ext/farce/unshared_signal.c
|
|
@@ -79,6 +80,7 @@ files:
|
|
|
79
80
|
- ext/farce/unsupported.c
|
|
80
81
|
- ext/farce/vector.c
|
|
81
82
|
- ext/farce/weak_map.c
|
|
83
|
+
- ext/farce/weak_references.h
|
|
82
84
|
- ext/java/org/farce/BoundedMap.java
|
|
83
85
|
- ext/java/org/farce/FiberScheduler.java
|
|
84
86
|
- ext/java/org/farce/PriorityKey.java
|
|
@@ -196,6 +198,7 @@ files:
|
|
|
196
198
|
- lib/farce/engine/ruby/shared/vault.rb
|
|
197
199
|
- lib/farce/engine/ruby/shared/vault_weak_map.rb
|
|
198
200
|
- lib/farce/engine/ruby/shared/weak_atom.rb
|
|
201
|
+
- lib/farce/engine/ruby/shared/weak_cache_mirror.rb
|
|
199
202
|
- lib/farce/engine/ruby/shared/weak_map.rb
|
|
200
203
|
- lib/farce/engine/shared.rb
|
|
201
204
|
- lib/farce/engine/shared/atom.rb
|
|
@@ -220,6 +223,7 @@ files:
|
|
|
220
223
|
- lib/farce/engine/shared/strict_tree_map.rb
|
|
221
224
|
- lib/farce/engine/shared/transaction_map_backend.rb
|
|
222
225
|
- lib/farce/engine/shared/trie.rb
|
|
226
|
+
- lib/farce/engine/shared/trie/ignore_case.rb
|
|
223
227
|
- lib/farce/engine/shared/trie_builder.rb
|
|
224
228
|
- lib/farce/engine/shared/unshareable.rb
|
|
225
229
|
- lib/farce/engine/shared/unshared_atom.rb
|
|
@@ -427,6 +431,7 @@ files:
|
|
|
427
431
|
- lib/farce/strict/tree_map.rb
|
|
428
432
|
- lib/farce/strict/vector.rb
|
|
429
433
|
- lib/farce/strict/weak_atom.rb
|
|
434
|
+
- lib/farce/strict/weak_cache.rb
|
|
430
435
|
- lib/farce/strict/weak_key_map.rb
|
|
431
436
|
- lib/farce/strict/weak_map.rb
|
|
432
437
|
- lib/farce/strict/weak_set.rb
|