messagepack 1.0.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +773 -0
  3. data/Rakefile +8 -0
  4. data/docs/Gemfile +7 -0
  5. data/docs/README.md +85 -0
  6. data/docs/_config.yml +137 -0
  7. data/docs/_guides/index.adoc +14 -0
  8. data/docs/_guides/io-streaming.adoc +226 -0
  9. data/docs/_guides/migration.adoc +218 -0
  10. data/docs/_guides/performance.adoc +189 -0
  11. data/docs/_pages/buffer.adoc +85 -0
  12. data/docs/_pages/extension-types.adoc +117 -0
  13. data/docs/_pages/factory-pattern.adoc +115 -0
  14. data/docs/_pages/index.adoc +20 -0
  15. data/docs/_pages/serialization.adoc +159 -0
  16. data/docs/_pages/streaming.adoc +97 -0
  17. data/docs/_pages/symbol-extension.adoc +69 -0
  18. data/docs/_pages/timestamp-extension.adoc +88 -0
  19. data/docs/_references/api.adoc +360 -0
  20. data/docs/_references/extensions.adoc +198 -0
  21. data/docs/_references/format.adoc +301 -0
  22. data/docs/_references/index.adoc +14 -0
  23. data/docs/_tutorials/extension-types.adoc +170 -0
  24. data/docs/_tutorials/getting-started.adoc +165 -0
  25. data/docs/_tutorials/index.adoc +14 -0
  26. data/docs/_tutorials/thread-safety.adoc +157 -0
  27. data/docs/index.adoc +77 -0
  28. data/docs/lychee.toml +42 -0
  29. data/lib/messagepack/bigint.rb +131 -0
  30. data/lib/messagepack/buffer.rb +534 -0
  31. data/lib/messagepack/core_ext.rb +34 -0
  32. data/lib/messagepack/error.rb +24 -0
  33. data/lib/messagepack/extensions/base.rb +55 -0
  34. data/lib/messagepack/extensions/registry.rb +154 -0
  35. data/lib/messagepack/extensions/symbol.rb +38 -0
  36. data/lib/messagepack/extensions/timestamp.rb +110 -0
  37. data/lib/messagepack/extensions/value.rb +38 -0
  38. data/lib/messagepack/factory.rb +349 -0
  39. data/lib/messagepack/format.rb +99 -0
  40. data/lib/messagepack/packer.rb +702 -0
  41. data/lib/messagepack/symbol.rb +4 -0
  42. data/lib/messagepack/time.rb +29 -0
  43. data/lib/messagepack/timestamp.rb +4 -0
  44. data/lib/messagepack/unpacker.rb +1418 -0
  45. data/lib/messagepack/version.rb +5 -0
  46. data/lib/messagepack.rb +81 -0
  47. metadata +94 -0
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Messagepack
4
+ # Extension registries for packer and unpacker.
5
+ #
6
+ # The packer registry maps Ruby classes to extension type IDs and packing procs.
7
+ # The unpacker registry maps extension type IDs to unpacking procs.
8
+ #
9
+ module ExtensionRegistry
10
+ # Packer registry for serializing custom types.
11
+ #
12
+ # Uses hash-based lookup with ancestor chain caching.
13
+ #
14
+ class Packer
15
+ def initialize
16
+ @registry = {} # klass => [type_id, proc, flags]
17
+ @cache = {} # klass => [type_id, proc, flags] (ancestor cache)
18
+ end
19
+
20
+ def dup
21
+ copy = self.class.new
22
+ copy.instance_variable_set(:@registry, @registry.dup)
23
+ copy.instance_variable_set(:@cache, {})
24
+ copy
25
+ end
26
+
27
+ alias clone dup
28
+
29
+ def register(type_id, klass, proc, flags: 0)
30
+ @registry[klass] = [type_id, proc, flags]
31
+ @cache.clear
32
+ end
33
+
34
+ def lookup(value)
35
+ klass = value.class
36
+ return @registry[klass] if @registry.key?(klass)
37
+
38
+ # Check cache
39
+ return @cache[klass] if @cache.key?(klass)
40
+
41
+ # Search ancestors and modules
42
+ @registry.each do |registered_class, data|
43
+ # Check for inheritance (klass <= registered_class)
44
+ if klass <= registered_class
45
+ @cache[klass] = data
46
+ return data
47
+ end
48
+
49
+ # Check if registered_class is a module (but not a class) and klass includes it
50
+ # In Ruby, Class is not a subclass of Module, so we need to check the class
51
+ if registered_class.is_a?(Class)
52
+ # registered_class is a Class, already handled by inheritance check above
53
+ next
54
+ end
55
+
56
+ # registered_class is a Module
57
+ # Check if klass includes the module
58
+ if klass.include?(registered_class)
59
+ @cache[klass] = data
60
+ return data
61
+ end
62
+
63
+ # Check if the object's singleton class includes the module
64
+ # This handles cases like: obj.extend(Mod)
65
+ # Some values don't support singleton_class:
66
+ # - Immediate values: nil, true, false, integers, floats, symbols
67
+ # - Frozen values (frozen_string_literal: true makes string literals frozen)
68
+ unless value.nil? || value == true || value == false ||
69
+ value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(Symbol) ||
70
+ value.frozen?
71
+ if value.singleton_class.include?(registered_class)
72
+ @cache[klass] = data
73
+ return data
74
+ end
75
+ end
76
+ end
77
+
78
+ nil
79
+ end
80
+
81
+ def registered_types
82
+ @registry.map { |klass, (type_id, proc, flags)|
83
+ { type: type_id, class: klass, packer: proc }
84
+ }
85
+ end
86
+
87
+ def type_registered?(klass_or_type)
88
+ if klass_or_type.is_a?(Integer)
89
+ @registry.any? { |_, (type_id, _, _)| type_id == klass_or_type }
90
+ else
91
+ @registry.key?(klass_or_type)
92
+ end
93
+ end
94
+
95
+ def clear
96
+ @registry.clear
97
+ @cache.clear
98
+ end
99
+ end
100
+
101
+ # Unpacker registry for deserializing custom types.
102
+ #
103
+ # Uses direct array for O(1) type ID lookup.
104
+ #
105
+ class Unpacker
106
+ ARRAY_SIZE = 256 # Signed byte range: -128 to 127
107
+
108
+ def initialize
109
+ @array = ::Array.new(ARRAY_SIZE) # [klass, proc, flags]
110
+ end
111
+
112
+ def dup
113
+ copy = self.class.new
114
+ copy.instance_variable_set(:@array, @array.dup)
115
+ copy
116
+ end
117
+
118
+ alias clone dup
119
+
120
+ def register(type_id, klass, proc, flags: 0)
121
+ index = type_id + 128 # Offset for signed byte
122
+ raise IndexError, "type_id out of range: #{type_id}" unless (0...ARRAY_SIZE).cover?(index)
123
+ @array[index] = [klass, proc, flags]
124
+ end
125
+
126
+ def lookup(type_id)
127
+ index = type_id + 128
128
+ return nil unless (0...ARRAY_SIZE).cover?(index)
129
+ @array[index]
130
+ end
131
+
132
+ def registered_types
133
+ @array.each_with_index.map { |(klass, proc, flags), index|
134
+ next unless proc # Include entries that have a proc, even if klass is nil
135
+ { type: index - 128, class: klass, unpacker: proc }
136
+ }.compact
137
+ end
138
+
139
+ def type_registered?(klass_or_type)
140
+ if klass_or_type.is_a?(Integer)
141
+ index = klass_or_type + 128
142
+ # Check if any data (klass, proc, or flags) exists at this type ID index
143
+ (0...ARRAY_SIZE).cover?(index) && @array[index]&.at(1)
144
+ else
145
+ @array.any? { |(klass, _, _)| klass == klass_or_type }
146
+ end
147
+ end
148
+
149
+ def clear
150
+ @array.fill(nil)
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Symbol extension for MessagePack.
4
+ #
5
+ # This adds support for serializing Symbol objects directly.
6
+ # When registered, symbols are serialized as their string representation.
7
+ #
8
+ class Symbol
9
+ # to_msgpack_ext is supposed to return a binary string.
10
+ # The canonical way to do it for symbols would be:
11
+ # [to_s].pack('A*')
12
+ # However in this instance we can take a shortcut
13
+ if method_defined?(:name)
14
+ alias_method :to_msgpack_ext, :name
15
+ else
16
+ alias_method :to_msgpack_ext, :to_s
17
+ end
18
+
19
+ # Reconstruct symbol from binary payload.
20
+ #
21
+ # @param data [String] Binary payload (symbol name as string)
22
+ # @return [Symbol] The reconstructed symbol
23
+ #
24
+ def self.from_msgpack_ext(data)
25
+ # from_msgpack_ext is supposed to parse a binary string.
26
+ # The canonical way to do it for symbols would be:
27
+ # data.unpack1('A*').to_sym
28
+ # However in this instance we can take a shortcut
29
+
30
+ # We assume the string encoding is UTF-8, and let Ruby create either
31
+ # an ASCII symbol or UTF-8 symbol.
32
+ data.force_encoding(Encoding::UTF_8).to_sym
33
+ rescue EncodingError
34
+ # If somehow the string wasn't valid UTF-8 not valid ASCII, we fallback
35
+ # to what has been the historical behavior of creating a binary symbol
36
+ data.force_encoding(Encoding::BINARY).to_sym
37
+ end
38
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'value'
4
+
5
+ module Messagepack
6
+ # Timestamp extension type (type -1).
7
+ #
8
+ # Represents a high-resolution timestamp with second and nanosecond precision.
9
+ # Implements the MessagePack timestamp specification:
10
+ # https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
11
+ #
12
+ class Timestamp
13
+ TYPE = -1
14
+
15
+ TIMESTAMP32_MAX_SEC = (1 << 32) - 1
16
+ TIMESTAMP64_MAX_SEC = (1 << 34) - 1
17
+
18
+ attr_reader :sec, :nsec
19
+
20
+ def initialize(sec, nsec)
21
+ @sec = sec
22
+ @nsec = nsec
23
+ end
24
+
25
+ # Deserialize from binary payload.
26
+ #
27
+ # @param data [String] Binary payload from MessagePack
28
+ # @return [Timestamp] Deserialized timestamp
29
+ #
30
+ def self.from_msgpack_ext(data)
31
+ case data.bytesize
32
+ when 4
33
+ # timestamp32 (sec: uint32be)
34
+ sec = data.unpack1('L>')
35
+ new(sec, 0)
36
+ when 8
37
+ # timestamp64 (nsec: uint30be, sec: uint34be)
38
+ n, s = data.unpack('L>2')
39
+ sec = ((n & 0b11) << 32) | s
40
+ nsec = n >> 2
41
+ new(sec, nsec)
42
+ when 12
43
+ # timestamp96 (nsec: uint32be, sec: int64be)
44
+ nsec, sec = data.unpack('L>q>')
45
+ new(sec, nsec)
46
+ else
47
+ raise MalformedFormatError, "Invalid timestamp data size: #{data.bytesize}"
48
+ end
49
+ end
50
+
51
+ # Serialize to binary payload.
52
+ #
53
+ # @return [String] Binary payload for MessagePack extension
54
+ #
55
+ def to_msgpack_ext
56
+ self.class.to_msgpack_ext(@sec, @nsec)
57
+ end
58
+
59
+ # Class helper for serialization.
60
+ #
61
+ # @param sec [Integer] Seconds since epoch
62
+ # @param nsec [Integer] Nanoseconds within second (0-999999999)
63
+ # @return [String] Binary payload
64
+ #
65
+ def self.to_msgpack_ext(sec, nsec)
66
+ if sec >= 0 && nsec >= 0 && sec <= TIMESTAMP64_MAX_SEC
67
+ if nsec == 0 && sec <= TIMESTAMP32_MAX_SEC
68
+ # timestamp32: 4 bytes
69
+ [sec].pack('L>')
70
+ else
71
+ # timestamp64: 8 bytes
72
+ nsec30 = nsec << 2
73
+ sec_high2 = sec >> 32
74
+ sec_low32 = sec & 0xffffffff
75
+ [nsec30 | sec_high2, sec_low32].pack('L>2')
76
+ end
77
+ else
78
+ # timestamp96: 12 bytes
79
+ [nsec, sec].pack('L>q>')
80
+ end
81
+ end
82
+
83
+ # Convert to Time object
84
+ def to_time
85
+ Time.at(@sec, @nsec, :nanosecond)
86
+ end
87
+
88
+ # Create from Time object
89
+ def self.from_time(time)
90
+ new(time.tv_sec, time.tv_nsec)
91
+ end
92
+
93
+ def ==(other)
94
+ other.class == self.class && @sec == other.sec && @nsec == other.nsec
95
+ end
96
+
97
+ alias eql? ==
98
+
99
+ def hash
100
+ [@sec, @nsec].hash
101
+ end
102
+
103
+ # String representation
104
+ def to_s
105
+ "Timestamp(#{@sec}, #{@nsec})"
106
+ end
107
+
108
+ alias inspect to_s
109
+ end
110
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Messagepack
4
+ # ExtensionValue represents a raw MessagePack extension type.
5
+ #
6
+ # This is used when unpacking extension types that don't have a
7
+ # registered handler, or for manual extension construction.
8
+ #
9
+ class ExtensionValue
10
+ attr_accessor :type, :payload
11
+
12
+ def initialize(type, payload)
13
+ @type = type
14
+ @payload = payload
15
+ end
16
+
17
+ def ==(other)
18
+ return false unless other.is_a?(ExtensionValue)
19
+ @type == other.type && @payload == other.payload
20
+ end
21
+
22
+ alias eql? ==
23
+
24
+ def hash
25
+ [@type, @payload].hash
26
+ end
27
+
28
+ # Convert back to MessagePack format
29
+ def to_msgpack(packer = nil)
30
+ if packer.is_a?(Messagepack::Packer)
31
+ packer.write_extension(@type, @payload)
32
+ packer
33
+ else
34
+ Messagepack::Packer.new.write_extension(@type, @payload).full_pack
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'packer'
4
+ require_relative 'unpacker'
5
+ require_relative 'extensions/registry'
6
+
7
+ module Messagepack
8
+ # Factory for creating packer/unpacker instances with custom type registration.
9
+ #
10
+ # Usage:
11
+ # factory = Messagepack::Factory.new
12
+ # factory.register_type(0x01, MyClass, packer: :to_msgpack, unpacker: :from_msgpack)
13
+ # packer = factory.packer
14
+ # unpacker = factory.unpacker
15
+ #
16
+ class Factory
17
+ attr_reader :packer_registry, :unpacker_registry
18
+
19
+ def initialize
20
+ @packer_registry = ExtensionRegistry::Packer.new
21
+ @unpacker_registry = ExtensionRegistry::Unpacker.new
22
+ @frozen = false
23
+ end
24
+
25
+ def freeze
26
+ @frozen = true
27
+ @packer_registry.freeze
28
+ @unpacker_registry.freeze
29
+ super
30
+ end
31
+
32
+ # Ensure that when the factory is duplicated (for pool), the registries are also duplicated
33
+ def initialize_copy(other)
34
+ super
35
+ @packer_registry = @packer_registry.dup
36
+ @unpacker_registry = @unpacker_registry.dup
37
+ @frozen = false
38
+ end
39
+
40
+ # Register a custom type for packing and unpacking.
41
+ #
42
+ # @param type_id [Integer] Extension type ID (-128 to 127)
43
+ # @param klass [Class] Ruby class to register
44
+ # @param options [Hash] Registration options
45
+ # @option options [Proc, Method, Symbol, String] :packer Serialization proc/method
46
+ # @option options [Proc, Method, Symbol, String] :unpacker Deserialization proc/method
47
+ # @option options [Boolean] :recursive Whether packer/unpacker is passed to proc
48
+ #
49
+ def register_type(type_id, klass, options = {})
50
+ raise FrozenError, "can't modify frozen Messagepack::Factory" if frozen?
51
+
52
+ packer = normalize_packer(options[:packer], klass)
53
+ unpacker = normalize_unpacker(options[:unpacker], klass)
54
+
55
+ register_type_internal(type_id, klass, packer, unpacker, options)
56
+ end
57
+
58
+ # Get list of registered types.
59
+ #
60
+ # @param selector [Symbol] :packer, :unpacker, or :both
61
+ # @return [Array<Hash>] List of registered types
62
+ #
63
+ def registered_types(selector = :both)
64
+ case selector
65
+ when :packer
66
+ @packer_registry.registered_types
67
+ when :unpacker
68
+ @unpacker_registry.registered_types
69
+ when :both
70
+ packer_types = @packer_registry.registered_types
71
+ unpacker_types = @unpacker_registry.registered_types
72
+
73
+ # Merge by type_id
74
+ all_types = {}
75
+ packer_types.each { |t| all_types[t[:type]] = t.merge(unpacker: nil) }
76
+ unpacker_types.each do |t|
77
+ if all_types.key?(t[:type])
78
+ all_types[t[:type]][:unpacker] = t[:unpacker]
79
+ else
80
+ all_types[t[:type]] = t.merge(packer: nil)
81
+ end
82
+ end
83
+
84
+ all_types.values
85
+ else
86
+ raise ArgumentError, "Invalid selector: #{selector}"
87
+ end
88
+ end
89
+
90
+ # Check if a type is registered.
91
+ #
92
+ # @param klass_or_type [Class, Integer] Class or type ID to check
93
+ # @param selector [Symbol] :packer, :unpacker, or :both
94
+ # @return [Boolean]
95
+ #
96
+ def type_registered?(klass_or_type, selector = :both)
97
+ case selector
98
+ when :packer
99
+ @packer_registry.type_registered?(klass_or_type)
100
+ when :unpacker
101
+ @unpacker_registry.type_registered?(klass_or_type)
102
+ when :both
103
+ @packer_registry.type_registered?(klass_or_type) ||
104
+ @unpacker_registry.type_registered?(klass_or_type)
105
+ else
106
+ raise ArgumentError, "Invalid selector: #{selector}"
107
+ end
108
+ end
109
+
110
+ # Create a new packer instance.
111
+ #
112
+ # @param io [IO] Optional IO object for streaming output
113
+ # @param options [Hash] Options to pass to Packer
114
+ # @return [Packer] New packer with registered types
115
+ #
116
+ def packer(io = nil, options = nil)
117
+ Packer.new(io, options).tap do |pk|
118
+ pk.extension_registry = @packer_registry.dup
119
+ end
120
+ end
121
+
122
+ # Create a new unpacker instance.
123
+ #
124
+ # @param io [IO] Optional IO object for streaming input
125
+ # @param options [Hash] Options to pass to Unpacker
126
+ # @return [Unpacker] New unpacker with registered types
127
+ #
128
+ def unpacker(io = nil, **options)
129
+ Unpacker.new(io, **options).tap do |uk|
130
+ uk.extension_registry = @unpacker_registry.dup
131
+ end
132
+ end
133
+
134
+ # Serialize an object to MessagePack binary.
135
+ #
136
+ # @param object Object to serialize
137
+ # @param io [IO] Optional IO to write to
138
+ # @param options [Hash] Options to pass to Packer
139
+ # @return [String, nil] Binary string if io is nil
140
+ #
141
+ def dump(object, *args)
142
+ io = args.first if args.first.respond_to?(:write)
143
+ options = args.last if args.last.is_a?(Hash)
144
+ packer(io, **(options || {})).tap { |pk| pk.write(object); pk.flush }.full_pack
145
+ end
146
+
147
+ # Deserialize MessagePack binary to Ruby object.
148
+ #
149
+ # @param data [String, IO] Binary data or IO to read from
150
+ # @param options [Hash] Options to pass to Unpacker
151
+ # @return [Object] Deserialized object
152
+ #
153
+ def load(data, options = nil)
154
+ # Check if data is an IO-like object
155
+ if data.respond_to?(:read)
156
+ unpacker(data, **(options || {})).full_unpack
157
+ else
158
+ unpacker(nil, **(options || {})).tap { |uk| uk.feed(data) }.full_unpack
159
+ end
160
+ end
161
+
162
+ alias :pack :dump
163
+ alias :unpack :load
164
+
165
+ # Create a pool of packers/unpackers for thread-safe reuse.
166
+ #
167
+ # @param size [Integer] Number of packers/unpackers in the pool
168
+ # @param options [Hash] Options for packer/unpacker creation
169
+ # @return [Pool] New pool instance
170
+ #
171
+ def pool(size = 1, **options)
172
+ Pool.new(frozen? ? self : dup.freeze, size, options)
173
+ end
174
+
175
+ private
176
+
177
+ def normalize_packer(packer, klass)
178
+ case packer
179
+ when nil
180
+ packer
181
+ when Proc
182
+ packer
183
+ when String, Symbol
184
+ # Create a proc that calls the method on the object being packed
185
+ ->(obj) { obj.send(packer) }
186
+ when Method
187
+ packer.to_proc
188
+ else
189
+ if packer.respond_to?(:call)
190
+ packer.method(:call).to_proc
191
+ else
192
+ raise ::TypeError, "invalid packer: #{packer.inspect}"
193
+ end
194
+ end
195
+ end
196
+
197
+ def normalize_unpacker(unpacker, klass)
198
+ case unpacker
199
+ when nil, Proc
200
+ unpacker
201
+ when String, Symbol
202
+ klass.method(unpacker).to_proc
203
+ when Method
204
+ unpacker.to_proc
205
+ else
206
+ if unpacker.respond_to?(:call)
207
+ unpacker.method(:call).to_proc
208
+ else
209
+ raise ::TypeError, "invalid unpacker: #{unpacker.inspect}"
210
+ end
211
+ end
212
+ end
213
+
214
+ def register_type_internal(type_id, klass, packer, unpacker, options)
215
+ # Validate oversized_integer_extension option
216
+ if options[:oversized_integer_extension]
217
+ unless klass == Integer
218
+ raise ArgumentError, "oversized_integer_extension can only be used with Integer class"
219
+ end
220
+ end
221
+
222
+ # Only use default methods when packer/unpacker are not specified at all
223
+ # (i.e., the keys don't exist in options)
224
+ has_packer = options.key?(:packer)
225
+ has_unpacker = options.key?(:unpacker)
226
+
227
+ if !has_packer && !has_unpacker && packer.nil?
228
+ packer = :to_msgpack_ext if klass.method_defined?(:to_msgpack_ext)
229
+ end
230
+
231
+ if !has_packer && !has_unpacker && unpacker.nil?
232
+ unpacker = :from_msgpack_ext if klass.respond_to?(:from_msgpack_ext)
233
+ end
234
+
235
+ # Normalize packer/unpacker (convert symbols to procs)
236
+ if packer.is_a?(Symbol)
237
+ method_name = packer
238
+ packer = ->(obj) { obj.send(method_name) }
239
+ end
240
+
241
+ if unpacker.is_a?(Symbol)
242
+ unpacker = klass.method(unpacker).to_proc
243
+ end
244
+
245
+ flags = 0
246
+ flags |= 0x01 if options[:recursive]
247
+ flags |= 0x02 if options[:oversized_integer_extension]
248
+
249
+ @packer_registry.register(type_id, klass, packer, flags: flags) if packer
250
+ @unpacker_registry.register(type_id, klass, unpacker, flags: flags) if unpacker
251
+ end
252
+
253
+ # Pool for thread-safe packer/unpacker reuse.
254
+ #
255
+ class Pool
256
+ def initialize(factory, size, options)
257
+ @factory = factory
258
+ @size = size
259
+ @options = options.empty? ? nil : options
260
+ @packers = []
261
+ @unpackers = []
262
+ @mutex = Mutex.new
263
+ end
264
+
265
+ # Deserialize data.
266
+ #
267
+ # @param data [String] Binary data
268
+ # @return [Object] Deserialized object
269
+ #
270
+ def load(data)
271
+ with_unpacker { |uk| uk.feed(data); uk.full_unpack }
272
+ end
273
+
274
+ # Serialize object.
275
+ #
276
+ # @param object Object to serialize
277
+ # @return [String] Binary data
278
+ #
279
+ def dump(object)
280
+ with_packer { |pk| pk.write(object); pk.full_pack }
281
+ end
282
+
283
+ alias :pack :dump
284
+ alias :unpack :load
285
+
286
+ # Execute block with a packer from the pool.
287
+ #
288
+ def with_packer
289
+ packer = nil
290
+ @mutex.synchronize do
291
+ packer = @packers.pop || @factory.packer(**(@options || {}))
292
+ end
293
+
294
+ yield packer
295
+ ensure
296
+ @mutex.synchronize { @packers << packer.reset } if packer
297
+ end
298
+
299
+ # Execute block with an unpacker from the pool.
300
+ #
301
+ def with_unpacker
302
+ unpacker = nil
303
+ @mutex.synchronize do
304
+ unpacker = @unpackers.pop || @factory.unpacker(**(@options || {}))
305
+ end
306
+
307
+ yield unpacker
308
+ ensure
309
+ @mutex.synchronize { @unpackers << unpacker.reset } if unpacker
310
+ end
311
+
312
+ # Get a packer from the pool and yield it to the block.
313
+ #
314
+ # @return [Object] Result of the block
315
+ #
316
+ def packer
317
+ packer = nil
318
+ @mutex.synchronize do
319
+ packer = @packers.pop || @factory.packer(**(@options || {}))
320
+ end
321
+
322
+ # Set frozen flag to prevent type registration
323
+ packer.freeze_for_pool
324
+
325
+ yield packer
326
+ ensure
327
+ @mutex.synchronize { @packers << packer.reset } if packer
328
+ end
329
+
330
+ # Get an unpacker from the pool and yield it to the block.
331
+ #
332
+ # @return [Object] Result of the block
333
+ #
334
+ def unpacker
335
+ unpacker = nil
336
+ @mutex.synchronize do
337
+ unpacker = @unpackers.pop || @factory.unpacker(**(@options || {}))
338
+ end
339
+
340
+ # Set frozen flag to prevent type registration
341
+ unpacker.freeze_for_pool
342
+
343
+ yield unpacker
344
+ ensure
345
+ @mutex.synchronize { @unpackers << unpacker.reset } if unpacker
346
+ end
347
+ end
348
+ end
349
+ end