Class: Farce::Vector
- Inherits:
-
Abstract::Vector
- Object
- Abstract::Collection
- Abstract::Vector
- Farce::Vector
- Includes:
- Shareable::Delegated
- Defined in:
- lib/farce/vector.rb
Overview
A Ractor-shareable vector with transfer modes for mutable values. Values stored through this vector's mode manager are automatically unwrapped.
Instance Method Summary collapse
-
#[](index) ⇒ BasicObject?
Read an index without waiting for atomic-update access.
-
#[]=(index, value) ⇒ BasicObject
Store a value, growing the vector if necessary.
-
#append(value, mode: nil, timeout: nil) ⇒ self, false
Append one value using the same options as #push.
-
#compare_and_set(index, expected, replacement, mode: nil, timeout: nil) ⇒ Boolean
Replace an existing index only if its value matches the expected value.
-
#compare_by_identity? ⇒ Boolean
Whether values are compared by identity.
-
#get(index, timeout: nil) ⇒ BasicObject?
Read an index after acquiring atomic-update access.
-
#initialize(source = nil, mode: :copy, compare_by_identity: false) ⇒ Vector
constructor
Valid modes are: *
:copy- The value will be copied between Ractors. -
#mode ⇒ Symbol
The default transfer mode.
-
#pop(timeout: nil) ⇒ BasicObject?
Remove and return the last value.
-
#push(value, mode: nil, timeout: nil) ⇒ self, false
Append a single value.
-
#shareable_values? ⇒ Boolean
Whether stored values must be Ractor-shareable.
-
#store(index, value, mode: nil, timeout: nil) ⇒ BasicObject, false
Store a value after acquiring atomic-update access.
-
#store_if_absent(index, mode: nil, timeout: nil) { ... } ⇒ BasicObject?
Compute and store a value only when the index is absent or contains nil.
-
#swap(index, replacement, mode: nil, timeout: nil) ⇒ BasicObject?
Replace an index and return its previous value, growing the vector if necessary.
-
#update(index, mode: nil, timeout: nil) {|value| ... } ⇒ BasicObject?
Atomically replace an index with the block result, growing the vector if necessary.
-
#upsert(index, initial, mode: nil, timeout: nil) {|value| ... } ⇒ BasicObject?
Store initial for an absent or nil index, otherwise replace it with the block result.
-
#wait_until_changed(index, expected, timeout: nil) ⇒ BasicObject?
Wait until an index no longer matches expected.
-
#wait_until_non_nil(index, timeout: nil) ⇒ BasicObject?
Wait until an index contains a non-nil value.
Methods included from Shareable
Methods inherited from Abstract::Vector
#&, #*, #+, #-, #<<, #as_extended_json, #as_json, #assoc, #at, #bsearch, #bsearch_index, #bson_type, #chunk, #chunk_while, #clear, #compact, #compact_blank, #concat, #deconstruct, #deep_dup, #difference, #dig, #drop, #drop_while, #each, #each_cons, #each_index, #each_slice, #entries, #excluding, #fetch, #fetch_values, #fifth, #filter_map, #first, #flat_map, #forty_two, #fourth, #from, #grep, #grep_v, #group_by, #in_groups, #in_groups_of, #in_order_of, #include?, #including, #index, #inquiry, #intersect?, #intersection, #last, #map, #max, #max_by, #maximum, #min, #min_by, #minimum, #minmax, #minmax_by, #pack, #partition, #pick, #pluck, #rassoc, #reject, #reverse, #reverse_each, #rfind, #rindex, #rotate, #sample, #second, #second_to_last, #select, #shuffle, #size, #slice, #slice_after, #slice_before, #slice_when, #sort, #sort_by, #split, #take, #take_while, #third, #third_to_last, #to, #to_a, #to_bson, #to_bson_normalized_value, #to_cbor, #to_fs, #to_h, #to_json, #to_msgpack, #to_param, #to_query, #to_sentence, #to_xml, #union, #uniq, #values_at, #wait_until, #wait_until_match, #wait_until_value, #wait_while, #wait_while_match, #wait_while_value, #zip, #|
Methods inherited from Abstract::Collection
#<<, [], #clear, #count, #each, #empty?, #filter, #include?, #join, #length, #member?, #reject, #select, #size, #to_a, #to_s
Methods included from Internal::Copyable
Constructor Details
#initialize(source = nil, mode: :copy, compare_by_identity: false) ⇒ Vector
Valid modes are:
:copy- The value will be copied between Ractors. This is the default mode.:make_shareable- The value will be made Ractor-shareable using Ractor.make_shareable.:move- The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.:mutable- A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.:local- The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.:proxy- The value will be wrapped in aFarce::Proxythat executes calls in the original Ractor.:raise- An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.:dedup- The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.:shareable_copy- The value will be copied and the copy will be made Ractor-shareable.
26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/farce/vector.rb', line 26 def initialize(source = nil, mode: :copy, compare_by_identity: false) raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array) unless true.equal?(compare_by_identity) || false.equal?(compare_by_identity) raise ArgumentError, "compare_by_identity must be true or false" end @compare_by_identity = compare_by_identity @manager = ModeManager.new(mode:) @vector = Internal::Vector.new(source&.map { @manager.wrap(it) }, compare_by_identity: true) super() end |
Instance Method Details
#[](index) ⇒ BasicObject?
Read an index without waiting for atomic-update access.
67 |
# File 'lib/farce/vector.rb', line 67 def [](index) = @manager.unwrap(@vector[index]) |
#[]=(index, value) ⇒ BasicObject
Store a value, growing the vector if necessary.
70 71 72 73 74 |
# File 'lib/farce/vector.rb', line 70 def []=(index, value) check_frozen! @vector[index] = @manager.wrap(value) value end |
#append(value, mode: nil, timeout: nil) ⇒ self, false
Append one value using the same options as #push.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/farce/vector.rb', line 19 class Vector < Farce::Abstract::Vector include Shareable::Delegated # @!macro modes # @param source [Array, nil] Initial values. The source array is not retained. # @param mode [Symbol] The default value transfer mode. # @param compare_by_identity [Boolean] Whether values are compared by identity. def initialize(source = nil, mode: :copy, compare_by_identity: false) raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array) unless true.equal?(compare_by_identity) || false.equal?(compare_by_identity) raise ArgumentError, "compare_by_identity must be true or false" end @compare_by_identity = compare_by_identity @manager = ModeManager.new(mode:) @vector = Internal::Vector.new(source&.map { @manager.wrap(it) }, compare_by_identity: true) super() end # @api private def transaction_wrapper(transaction) = Transaction::Vector.new(transaction, self, @vector, manager: @manager) # @return [Symbol] The default transfer mode. def mode = @manager.mode # @api private def marshal_dump entries = @vector.snapshot.map { Internal::MarshalSupport.value(it) } [1, entries, @manager, compare_by_identity?, frozen?] end # @api private def marshal_load(data) entries, @manager, identity, frozen = Internal::MarshalSupport.payload(data, 4) @compare_by_identity = identity @vector = Internal::Vector.new(compare_by_identity: true) publish_shareable entries.each { @vector.push(Internal::MarshalSupport.restore_value(it)) } Internal::MarshalSupport.freeze(self, frozen) end # (see Farce::Abstract::Vector#compare_by_identity?) def compare_by_identity? = @compare_by_identity # (see Farce::Abstract::Vector#shareable_values?) def shareable_values? = true # (see Farce::Abstract::Vector#[]) def [](index) = @manager.unwrap(@vector[index]) # (see Farce::Abstract::Vector#[]=) def []=(index, value) check_frozen! @vector[index] = @manager.wrap(value) value end # (see Farce::Abstract::Vector#get) def get(index, timeout: nil) = @manager.unwrap(@vector.get(index, timeout:)) # (see Farce::Abstract::Vector#store) # @param mode [Symbol, nil] The transfer mode, or nil to use the default. def store(index, value, mode: nil, timeout: nil) check_frozen! @manager.unwrap(@vector.store(index, @manager.wrap(value, mode:), timeout:)) end # (see Farce::Abstract::Vector#push) # @param mode [Symbol, nil] The transfer mode, or nil to use the default. def push(value, mode: nil, timeout: nil) check_frozen! @vector.push(@manager.wrap(value, mode:), timeout:) ? self : false end # (see Farce::Abstract::Vector#pop) def pop(timeout: nil) = @manager.unwrap(@vector.pop(timeout:)) # (see Farce::Abstract::Vector#swap) # @!macro modes # @param mode [Symbol, nil] The replacement's transfer mode, or nil to use the default. def swap(index, replacement, mode: nil, timeout: nil) check_frozen! @manager.unwrap(@vector.swap(index, @manager.wrap(replacement, mode:), timeout:)) end # (see Farce::Abstract::Vector#store_if_absent) # @param mode [Symbol, nil] The result's transfer mode, or nil to use the default. def store_if_absent(index, mode: nil, timeout: nil) raise LocalJumpError, "no block given" unless block_given? check_frozen! @manager.unwrap(@vector.store_if_absent(index, timeout:) do value = yield check_frozen! @manager.wrap(value, mode:) end) end # (see Farce::Abstract::Vector#update) # @!macro modes # @param mode [Symbol, nil] The result's transfer mode, or nil to use the default. def update(index, mode: nil, timeout: nil) raise LocalJumpError, "no block given" unless block_given? check_frozen! @manager.unwrap(@vector.update(index, timeout:) do |current| value = yield(@manager.unwrap(current)) check_frozen! @manager.wrap(value, mode:) end) end # (see Farce::Abstract::Vector#upsert) # @!macro modes # @param mode [Symbol, nil] The value transfer mode, or nil to use the default. def upsert(index, initial, mode: nil, timeout: nil) raise LocalJumpError, "no block given" unless block_given? check_frozen! @manager.unwrap(@vector.update(index, timeout:) do |current| value = if nil.equal?(current) initial else yielded = yield(@manager.unwrap(current)) check_frozen! yielded end @manager.wrap(value, mode:) end) end # (see Farce::Abstract::Vector#compare_and_set) # @!macro modes # @param mode [Symbol, nil] The replacement's transfer mode, or nil to use the default. def compare_and_set(index, expected, replacement, mode: nil, timeout: nil) check_frozen! deadline = timeout_deadline(timeout) expected = wrap_comparison(expected) wrapped = false loop do current = @vector[index] equal = values_equal?(current, expected) check_frozen! return false unless equal unless wrapped replacement = @manager.wrap(replacement, mode:) wrapped = true end return true if @vector.compare_and_set(index, current, replacement, timeout: remaining_timeout(deadline)) return false if nil.equal?(current) || (deadline && Clock.now >= deadline) end end # (see Farce::Abstract::Vector#wait_until_changed) def wait_until_changed(index, expected, timeout: nil) deadline = timeout_deadline(timeout) expected = wrap_comparison(expected) loop do current = @vector[index] return @manager.unwrap(current) unless values_equal?(current, expected) return if deadline && Clock.now >= deadline @vector.wait_until_changed(index, current, timeout: remaining_timeout(deadline)) end end # (see Farce::Abstract::Vector#wait_until_non_nil) def wait_until_non_nil(index, timeout: nil) = @manager.unwrap(@vector.wait_until_non_nil(index, timeout:)) protected def logical_value(value) = @manager.unwrap(value) def build_derived_vector(values) self.class.allocate.initialize_derived_vector(values, @manager, @compare_by_identity) end def initialize_derived_vector(values, manager, compare_by_identity) @manager = manager @compare_by_identity = compare_by_identity @vector = Internal::Vector.new(values, compare_by_identity: true) publish_shareable end attr_reader :manager def reusable_operand_snapshot(other) return unless other.is_a?(Farce::Vector) return unless other.manager.equal?(@manager) other.internal_vector.snapshot end private def inspect_value(inspector, value) = super(inspector, value, @manager) def derived_storage(value) = @manager.wrap(value) def freeze_backend = @vector def wrap_comparison(value) = @manager.wrap(value, mode: compare_by_identity? ? :local : :copy) def values_equal?(left, right) = @manager.same_value?(left, right, identity: compare_by_identity?) def timeout_deadline(timeout) return if timeout.nil? timeout = Float(timeout) raise ArgumentError, "timeout must be finite and non-negative" unless timeout.finite? && !timeout.negative? Clock.now + timeout end def remaining_timeout(deadline) return unless deadline remaining = deadline - Clock.now remaining.negative? ? 0 : remaining end end |
#compare_and_set(index, expected, replacement, mode: nil, timeout: nil) ⇒ Boolean
Replace an existing index only if its value matches the expected value. This never grows the vector. Matching uses the configured comparison mode. Valid modes are:
:copy- The value will be copied between Ractors. This is the default mode.:make_shareable- The value will be made Ractor-shareable using Ractor.make_shareable.:move- The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.:mutable- A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.:local- The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.:proxy- The value will be wrapped in aFarce::Proxythat executes calls in the original Ractor.:raise- An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.:dedup- The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.:shareable_copy- The value will be copied and the copy will be made Ractor-shareable.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/farce/vector.rb', line 150 def compare_and_set(index, expected, replacement, mode: nil, timeout: nil) check_frozen! deadline = timeout_deadline(timeout) expected = wrap_comparison(expected) wrapped = false loop do current = @vector[index] equal = values_equal?(current, expected) check_frozen! return false unless equal unless wrapped replacement = @manager.wrap(replacement, mode:) wrapped = true end return true if @vector.compare_and_set(index, current, replacement, timeout: remaining_timeout(deadline)) return false if nil.equal?(current) || (deadline && Clock.now >= deadline) end end |
#compare_by_identity? ⇒ Boolean
Returns Whether values are compared by identity.
61 |
# File 'lib/farce/vector.rb', line 61 def compare_by_identity? = @compare_by_identity |
#get(index, timeout: nil) ⇒ BasicObject?
Read an index after acquiring atomic-update access.
77 |
# File 'lib/farce/vector.rb', line 77 def get(index, timeout: nil) = @manager.unwrap(@vector.get(index, timeout:)) |
#mode ⇒ Symbol
Returns The default transfer mode.
42 |
# File 'lib/farce/vector.rb', line 42 def mode = @manager.mode |
#pop(timeout: nil) ⇒ BasicObject?
Remove and return the last value. This does not wait for an empty vector to become nonempty.
94 |
# File 'lib/farce/vector.rb', line 94 def pop(timeout: nil) = @manager.unwrap(@vector.pop(timeout:)) |
#push(value, mode: nil, timeout: nil) ⇒ self, false
Append a single value.
88 89 90 91 |
# File 'lib/farce/vector.rb', line 88 def push(value, mode: nil, timeout: nil) check_frozen! @vector.push(@manager.wrap(value, mode:), timeout:) ? self : false end |
#shareable_values? ⇒ Boolean
Returns Whether stored values must be Ractor-shareable.
64 |
# File 'lib/farce/vector.rb', line 64 def shareable_values? = true |
#store(index, value, mode: nil, timeout: nil) ⇒ BasicObject, false
Store a value after acquiring atomic-update access.
81 82 83 84 |
# File 'lib/farce/vector.rb', line 81 def store(index, value, mode: nil, timeout: nil) check_frozen! @manager.unwrap(@vector.store(index, @manager.wrap(value, mode:), timeout:)) end |
#store_if_absent(index, mode: nil, timeout: nil) { ... } ⇒ BasicObject?
Compute and store a value only when the index is absent or contains nil.
106 107 108 109 110 111 112 113 114 |
# File 'lib/farce/vector.rb', line 106 def store_if_absent(index, mode: nil, timeout: nil) raise LocalJumpError, "no block given" unless block_given? check_frozen! @manager.unwrap(@vector.store_if_absent(index, timeout:) do value = yield check_frozen! @manager.wrap(value, mode:) end) end |
#swap(index, replacement, mode: nil, timeout: nil) ⇒ BasicObject?
Replace an index and return its previous value, growing the vector if necessary. Valid modes are:
:copy- The value will be copied between Ractors. This is the default mode.:make_shareable- The value will be made Ractor-shareable using Ractor.make_shareable.:move- The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.:mutable- A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.:local- The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.:proxy- The value will be wrapped in aFarce::Proxythat executes calls in the original Ractor.:raise- An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.:dedup- The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.:shareable_copy- The value will be copied and the copy will be made Ractor-shareable.
99 100 101 102 |
# File 'lib/farce/vector.rb', line 99 def swap(index, replacement, mode: nil, timeout: nil) check_frozen! @manager.unwrap(@vector.swap(index, @manager.wrap(replacement, mode:), timeout:)) end |
#update(index, mode: nil, timeout: nil) {|value| ... } ⇒ BasicObject?
Atomically replace an index with the block result, growing the vector if necessary. Valid modes are:
:copy- The value will be copied between Ractors. This is the default mode.:make_shareable- The value will be made Ractor-shareable using Ractor.make_shareable.:move- The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.:mutable- A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.:local- The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.:proxy- The value will be wrapped in aFarce::Proxythat executes calls in the original Ractor.:raise- An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.:dedup- The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.:shareable_copy- The value will be copied and the copy will be made Ractor-shareable.
119 120 121 122 123 124 125 126 127 |
# File 'lib/farce/vector.rb', line 119 def update(index, mode: nil, timeout: nil) raise LocalJumpError, "no block given" unless block_given? check_frozen! @manager.unwrap(@vector.update(index, timeout:) do |current| value = yield(@manager.unwrap(current)) check_frozen! @manager.wrap(value, mode:) end) end |
#upsert(index, initial, mode: nil, timeout: nil) {|value| ... } ⇒ BasicObject?
Store initial for an absent or nil index, otherwise replace it with the block result. Valid modes are:
:copy- The value will be copied between Ractors. This is the default mode.:make_shareable- The value will be made Ractor-shareable using Ractor.make_shareable.:move- The value will be moved between Ractors. This saves memory compared to copying, and supports values that can't be copied but moved (like IO objects). However, the value will no longer be accessible on the Ractor that pushed it.:mutable- A Mutable instance will be created for the value. This isn't done recursively and thus will fail for nested unshareable values.:local- The value will be kept local to the Ractor that pushed it. Another ractor trying to receive it will get an error. Useful for usage contained within a single Ractor.:proxy- The value will be wrapped in aFarce::Proxythat executes calls in the original Ractor.:raise- An error will be raised if the value is not Ractor-shareable. Useful for enforcing shareability.:dedup- The value will be deduplicated using Farce.dedup, then made Ractor-shareable. This may update and freeze the original. Already-shareable values pass through unchanged.:shareable_copy- The value will be copied and the copy will be made Ractor-shareable.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/farce/vector.rb', line 132 def upsert(index, initial, mode: nil, timeout: nil) raise LocalJumpError, "no block given" unless block_given? check_frozen! @manager.unwrap(@vector.update(index, timeout:) do |current| value = if nil.equal?(current) initial else yielded = yield(@manager.unwrap(current)) check_frozen! yielded end @manager.wrap(value, mode:) end) end |
#wait_until_changed(index, expected, timeout: nil) ⇒ BasicObject?
Wait until an index no longer matches expected. Absent indexes are observed as nil.
170 171 172 173 174 175 176 177 178 179 |
# File 'lib/farce/vector.rb', line 170 def wait_until_changed(index, expected, timeout: nil) deadline = timeout_deadline(timeout) expected = wrap_comparison(expected) loop do current = @vector[index] return @manager.unwrap(current) unless values_equal?(current, expected) return if deadline && Clock.now >= deadline @vector.wait_until_changed(index, current, timeout: remaining_timeout(deadline)) end end |
#wait_until_non_nil(index, timeout: nil) ⇒ BasicObject?
Wait until an index contains a non-nil value.
182 |
# File 'lib/farce/vector.rb', line 182 def wait_until_non_nil(index, timeout: nil) = @manager.unwrap(@vector.wait_until_non_nil(index, timeout:)) |