snow-data 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.
@@ -0,0 +1,8 @@
1
+ # This file is part of ruby-snowdata.
2
+ # Copyright (c) 2013 Noel Raymond Cower. All rights reserved.
3
+ # See COPYING for license details.
4
+
5
+ require 'snow-data/version'
6
+ require 'snow-data/snowdata_bindings'
7
+ require 'snow-data/c_struct'
8
+ require 'snow-data/memory'
@@ -0,0 +1,724 @@
1
+ # This file is part of ruby-snowdata.
2
+ # Copyright (c) 2013 Noel Raymond Cower. All rights reserved.
3
+ # See COPYING for license details.
4
+
5
+ require 'snow-data/snowdata_bindings'
6
+ require 'snow-data/memory'
7
+
8
+ module Snow
9
+
10
+
11
+ class CStruct
12
+
13
+ #
14
+ # Info for a struct member. Defines a member's name, type, size, length,
15
+ # alignment, and offset.
16
+ #
17
+ StructMemberInfo = Struct.new(:name, :type, :size, :length, :alignment, :offset)
18
+
19
+
20
+
21
+ #
22
+ # Struct base class. Not to be used directly, as it does not provide all the
23
+ # constants and methods necessary for a struct.
24
+ #
25
+ class StructBase < ::Snow::Memory
26
+
27
+ @@long_inspect = false
28
+
29
+ #
30
+ # Whether long inspect strings are enabled. By default, they are disabled.
31
+ #
32
+ # Long inspect strings can be useful for debugging, sepcially if you want to
33
+ # see the value, length, and alignment of every struct member in inspect
34
+ # strings. Otherwise, you can safely leave this disabled.
35
+ #
36
+ def self.long_inspect=(enabled)
37
+ @@long_inspect = !!enabled
38
+ end
39
+
40
+
41
+ def self.long_inspect
42
+ @@long_inspect
43
+ end
44
+
45
+
46
+ #
47
+ # Returns the offset of a member.
48
+ #
49
+ def self.offset_of(member)
50
+ self.MEMBERS_HASH[member].offset
51
+ end
52
+
53
+
54
+ #
55
+ # Returns the type name of a member.
56
+ #
57
+ def self.type_of(member)
58
+ self.MEMBERS_HASH[member].type
59
+ end
60
+
61
+
62
+ #
63
+ # Returns the size in bytes of a member.
64
+ #
65
+ def self.bytesize_of(member)
66
+ self.MEMBERS_HASH[member].size
67
+ end
68
+
69
+
70
+ #
71
+ # Returns the alignment of a member.
72
+ #
73
+ def self.alignment_of(member)
74
+ self.MEMBERS_HASH[member].alignment
75
+ end
76
+
77
+
78
+ #
79
+ # Returns the length of a member.
80
+ #
81
+ def self.length_of(member)
82
+ self.MEMBERS_HASH[member].length
83
+ end
84
+
85
+
86
+
87
+ #
88
+ # Returns the address of a member. This address is only valid for the
89
+ # receiver.
90
+ #
91
+ def address_of(member)
92
+ self.address + self.class.offset_of(member)
93
+ end
94
+
95
+
96
+ #
97
+ # Returns the offset of a member.
98
+ #
99
+ def offset_of(member)
100
+ self.class.offset_of(member)
101
+ end
102
+
103
+
104
+ #
105
+ # Returns the size in bytes of a member.
106
+ #
107
+ def bytesize_of(member)
108
+ self.class.bytesize_of(member)
109
+ end
110
+
111
+
112
+ #
113
+ # Returns the alignment of a member.
114
+ #
115
+ def alignment_of(member)
116
+ self.class.alignment_of(member)
117
+ end
118
+
119
+
120
+ #
121
+ # Returns the length of a member.
122
+ #
123
+ def length_of(member)
124
+ self.class.length_of(member)
125
+ end
126
+
127
+
128
+ #
129
+ # Returns the type name of a member.
130
+ #
131
+ def type_of(member)
132
+ self.class.type_of(member)
133
+ end
134
+
135
+
136
+ #
137
+ # Returns a hash of all of the struct's member names to their values.
138
+ #
139
+ def to_h
140
+ self.class::MEMBERS.inject({}) do |hash, member|
141
+ length = member.length
142
+ name = member.name
143
+
144
+ hash[name] = if length > 1
145
+ (0 ... length).map { |index| __send__(name, index) }
146
+ else
147
+ __send__(name)
148
+ end
149
+
150
+ hash
151
+ end
152
+ end
153
+
154
+
155
+ #
156
+ # Returns a string describing the struct, including its classname, object
157
+ # ID, address, size, and alignment. In addition, if long_inspect is enabled,
158
+ # then it will also include the values of the struct's members.
159
+ #
160
+ def inspect
161
+ id_text = __id__.to_s(16).rjust(14, ?0)
162
+ addr_text = self.address.to_s(16).rjust(14, ?0)
163
+
164
+ member_text = if ! null? && self.class.long_inspect
165
+ # Get member text
166
+ all_members_text = (self.class::MEMBERS.map do |member|
167
+ name = member.name
168
+ type = member.type
169
+ align = member.alignment
170
+ length = member.length
171
+ length_decl = length > 1 ? "[#{length}]" : ''
172
+
173
+ values_text = if length > 1
174
+ single_member_text = (0 ... length).map { |index|
175
+ "[#{ index }]=#{ self.__send__(member.name, index).inspect }"
176
+ }.join(', ')
177
+
178
+ "{ #{ single_member_text } }"
179
+ else
180
+ self.__send__(member.name).inspect
181
+ end
182
+
183
+ "#{ name }:#{ type }#{ length_decl }:#{ align }=#{ values_text }"
184
+
185
+ end).join('; ')
186
+
187
+ " #{all_members_text}"
188
+ else # member_text = if ...
189
+ # Skip members
190
+ ''
191
+ end # member_text = if ...
192
+
193
+ "<#{ self.class }:0x#{ id_text } *0x#{ addr_text }:#{ self.bytesize }:#{ self.alignment }#{member_text}>"
194
+ end
195
+
196
+
197
+ end # class StructBase
198
+
199
+
200
+ #
201
+ # Returns whether num is a power of two and nonzero.
202
+ #
203
+ def self.power_of_two?(num)
204
+ ((num & (num - 1)) == 0) && (num != 0)
205
+ end
206
+
207
+
208
+ #
209
+ # Returns an encoding for a struct member with the given name, type,
210
+ # length, and alignment. The type must be a string or symbol, not a Class or
211
+ # other object. If no alignment is provided, it uses the default alignment for
212
+ # the type or the size of a pointer if no alignment can be found.
213
+ #
214
+ # #### Example
215
+ # CStruct.member_encoding(:foo, :float, 32, nil) # => "foo:float[32]:4"
216
+ #
217
+ def self.member_encoding(name, type, length: 1, alignment: nil)
218
+ type = type.intern
219
+ alignment = alignment || ALIGNMENTS[type] || ALIGNMENTS[:*]
220
+ raise ArgumentError, "Invalid length: #{length}. Must be > 0." if length < 1
221
+ raise ArgumentError, "Invalid alignment: #{alignment}. Must be a power of two." if ! power_of_two?(alignment)
222
+ "#{name}:#{type}[#{length}]:#{alignment}"
223
+ end
224
+
225
+
226
+ # The encoding regex. Just used to make reading encoding strings easy. Do not
227
+ # touch this.
228
+ #--
229
+ # Ordinarily I'd write a lexer for this sort of thing, but regex actually
230
+ # seems to work fine.
231
+ #++
232
+ ENCODING_REGEX = %r{
233
+ (?<name> # 0
234
+ [_a-zA-Z][_a-zA-Z\d]*
235
+ )
236
+ \s* \: \s*
237
+ (?<type> # 1
238
+ # Named struct type encoding - must match previously defined type
239
+ \* | [a-zA-Z_][a-zA-Z_0-9]*
240
+ )
241
+ (?<type_array_decl> \s* \[ \s* # 2
242
+ (?<type_array_count> \d+ ) # 3
243
+ \s* \] )?
244
+ (?<type_alignment_decl> \s* \: \s* # 4
245
+ (?<type_alignment> \d+ ) # 5
246
+ )?
247
+ \s* (?: ; | $ | \n) # terminator
248
+ }mx
249
+
250
+ # Alignemnts for default types.
251
+ ALIGNMENTS = {
252
+ :char => 1,
253
+ :signed_char => 1,
254
+ :unsigned_char => 1,
255
+ :uint8_t => Memory::SIZEOF_UINT8_T,
256
+ :int8_t => Memory::SIZEOF_INT8_T,
257
+ :short => Memory::SIZEOF_SHORT,
258
+ :unsigned_short => Memory::SIZEOF_SHORT,
259
+ :uint16_t => Memory::SIZEOF_UINT16_T,
260
+ :int16_t => Memory::SIZEOF_INT16_T,
261
+ :int32_t => Memory::SIZEOF_INT32_T,
262
+ :uint32_t => Memory::SIZEOF_UINT32_T,
263
+ :uint64_t => Memory::SIZEOF_UINT64_T,
264
+ :int64_t => Memory::SIZEOF_INT64_T,
265
+ :unsigned_long => Memory::SIZEOF_LONG,
266
+ :unsigned_long_long => Memory::SIZEOF_LONG_LONG,
267
+ :long => Memory::SIZEOF_LONG,
268
+ :long_long => Memory::SIZEOF_LONG_LONG,
269
+ :int => Memory::SIZEOF_INT,
270
+ :unsigned_int => Memory::SIZEOF_INT,
271
+ :float => Memory::SIZEOF_FLOAT,
272
+ :double => Memory::SIZEOF_DOUBLE,
273
+ :size_t => Memory::SIZEOF_SIZE_T,
274
+ :ptrdiff_t => Memory::SIZEOF_PTRDIFF_T,
275
+ :intptr_t => Memory::SIZEOF_INTPTR_T,
276
+ :uintptr_t => Memory::SIZEOF_UINTPTR_T
277
+ }
278
+
279
+ # Sizes of default types.
280
+ SIZES = {
281
+ :char => 1,
282
+ :signed_char => 1,
283
+ :unsigned_char => 1,
284
+ :uint8_t => Memory::SIZEOF_UINT8_T,
285
+ :int8_t => Memory::SIZEOF_INT8_T,
286
+ :short => Memory::SIZEOF_SHORT,
287
+ :unsigned_short => Memory::SIZEOF_SHORT,
288
+ :uint16_t => Memory::SIZEOF_UINT16_T,
289
+ :int16_t => Memory::SIZEOF_INT16_T,
290
+ :int32_t => Memory::SIZEOF_INT32_T,
291
+ :uint32_t => Memory::SIZEOF_UINT32_T,
292
+ :uint64_t => Memory::SIZEOF_UINT64_T,
293
+ :int64_t => Memory::SIZEOF_INT64_T,
294
+ :unsigned_long => Memory::SIZEOF_LONG,
295
+ :unsigned_long_long => Memory::SIZEOF_LONG_LONG,
296
+ :long => Memory::SIZEOF_LONG,
297
+ :long_long => Memory::SIZEOF_LONG_LONG,
298
+ :int => Memory::SIZEOF_INT,
299
+ :unsigned_int => Memory::SIZEOF_INT,
300
+ :float => Memory::SIZEOF_FLOAT,
301
+ :double => Memory::SIZEOF_DOUBLE,
302
+ :size_t => Memory::SIZEOF_SIZE_T,
303
+ :ptrdiff_t => Memory::SIZEOF_PTRDIFF_T,
304
+ :intptr_t => Memory::SIZEOF_INTPTR_T,
305
+ :uintptr_t => Memory::SIZEOF_UINTPTR_T
306
+ }
307
+
308
+ # Used for getters/setters on Memory objects. Simply maps short type names to
309
+ # their long-form type names.
310
+ LONG_NAMES = {
311
+ # char
312
+ :c => :char,
313
+ :sc => :signed_char,
314
+ :uc => :unsigned_char,
315
+ :ui8 => :uint8_t,
316
+ :i8 => :int8_t,
317
+ # short (uint16_t)
318
+ :s => :short,
319
+ :us => :unsigned_short,
320
+ :ui16 => :uint16_t,
321
+ :i16 => :int16_t,
322
+ # int32
323
+ :i32 => :int32_t,
324
+ :ui32 => :uint32_t,
325
+ :ui64 => :uint64_t,
326
+ :i64 => :int64_t,
327
+ :ul => :unsigned_long,
328
+ :ull => :unsigned_long_long,
329
+ :l => :long,
330
+ :ll => :long_long,
331
+ :i => :int,
332
+ :ui => :unsigned_int,
333
+ :f => :float,
334
+ :d => :double,
335
+ :zu => :size_t,
336
+ :td => :ptrdiff_t,
337
+ :ip => :intptr_t,
338
+ :uip => :uintptr_t,
339
+ :* => :intptr_t # pointers always stored at intptr_t
340
+ }
341
+
342
+
343
+ #
344
+ # call-seq:
345
+ # add_type(name, klass) => klass
346
+ #
347
+ # Adds a type as a possible member type for structs. Types registered this way
348
+ # can be used as a member type by using the name provided to #add_type in
349
+ # struct encodings.
350
+ #
351
+ def self.add_type(name, klass)
352
+ name = name.intern
353
+
354
+ raise "No type name provided" if !name
355
+
356
+ ALIGNMENTS[name] = klass::ALIGNMENT
357
+ SIZES[name] = klass::SIZE
358
+
359
+ getter = :"get_#{name}"
360
+ setter = :"set_#{name}"
361
+
362
+ Memory.class_exec do
363
+
364
+ define_method(getter) do |offset|
365
+ wrapper = klass.__wrap__(self.address + offset, klass::SIZE, klass::ALIGNMENT)
366
+ wrapper.instance_variable_set(:@__base_memory__, self)
367
+ wrapper
368
+ end # getter
369
+
370
+ define_method(setter) do |offset, data|
371
+ raise "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
372
+ local_addr = self.address + offset
373
+ if !data.respond_to?(:address) || local_addr != data.address
374
+ copy!(data, offset, 0, klass::SIZE)
375
+ end
376
+
377
+ data
378
+ end # setter
379
+
380
+ end # class_exec
381
+
382
+ klass
383
+ end
384
+
385
+
386
+ #
387
+ # call-seq:
388
+ # new(name, encoding) => Class
389
+ # new(encoding) => Class
390
+ #
391
+ # Creates a new C-struct class and returns it. Optionally, if a name is
392
+ # provided, it is also added as a class under the CStruct class.
393
+ #
394
+ # In the first form when a name is provided, the name must be valid for a
395
+ # constant and be unique among CStruct types. The resulting type will be set
396
+ # as a constant under the CStruct class. So, for example:
397
+ #
398
+ # CStruct.new(:SomeStruct, 'member: float') # => Snow::CStruct::SomeStruct
399
+ # CStruct::SomeStruct.new # => <Snow::CStruct::SomeStruct:...>
400
+ #
401
+ # Additionally, this will register it as a possible member type for other
402
+ # structs, though struct types must be defined before they are used in other
403
+ # structs, otherwise there is no data for determining size, alignment, and so
404
+ # on for those structs and as such will likely result in an error.
405
+ #
406
+ # If no name is provided, the new class isn't set as a constant or usable as
407
+ # a member of another struct. To add it as a possible member type, you need to
408
+ # call ::add_type(name, klass). This will not register it as a constant under
409
+ # CStruct.
410
+ #
411
+ #
412
+ # ### Encodings
413
+ #
414
+ # Encodings are how you define C structs using Snow::CStruct. It's a fairly
415
+ # simple string format, defined as such:
416
+ #
417
+ # length ::= '[' integer ']'
418
+ # alignment ::= ':' integer
419
+ # typename ::= ':' Name
420
+ # member_name ::= Name
421
+ # member_decl ::= member_name typename [ length ] [ alignment ]
422
+ #
423
+ # So, for example, the encoding string "foo: float[4]:8" defines a C struct
424
+ # with a single member, `foo`, which is an array of 4 32-bit floats with an
425
+ # alignment of 8 bytes. By default, all types are aligned to their base type's
426
+ # size (e.g., "foo: float" would be algiend to 4 bytes) and all members have a
427
+ # length of 1 unless specified otherwise.
428
+ #
429
+ # A list of all types follows, including their short and long names, and their
430
+ # corresponding types in C. Short names are only provided for convenience and
431
+ # are generally not too useful except for reducing string length. They're
432
+ # expanded to their long-form names when the class is created.
433
+ #
434
+ # - `c / char => char`
435
+ # - `sc / signed_char => signed char`
436
+ # - `uc / unsigned_char => unsigned char`
437
+ # - `ui8 / uint8_t => uint8_t`
438
+ # - `i8 / int8_t => int8_t`
439
+ # - `s / short => short`
440
+ # - `us / unsigned_short => unsigned short`
441
+ # - `ui16 / uint16_t => uint16_t`
442
+ # - `i16 / int16_t => int16_t`
443
+ # - `i32 / int32_t => int32_t`
444
+ # - `ui32 / uint32_t => uint32_t`
445
+ # - `ui64 / uint64_t => uint64_t`
446
+ # - `i64 / int64_t => int64_t`
447
+ # - `ul / unsigned_long => unsigned long`
448
+ # - `ull / unsigned_long_long => unsigned long long`
449
+ # - `l / long => long`
450
+ # - `ll / long_long => long long`
451
+ # - `i / int => int`
452
+ # - `ui / unsigned_int => unsigned int`
453
+ # - `f / float => float`
454
+ # - `d / double => double`
455
+ # - `zu / size_t => size_t`
456
+ # - `td / ptrdiff_t => ptrdiff_t`
457
+ # - `ip / intptr_t => intptr_t`
458
+ # - `uip / uintptr_t => uintptr_t`
459
+ # - `* / intptr_t => void *` (stored as an `intptr_t`)
460
+ #
461
+ # In addition, any structs created with a name or added with #add_type are
462
+ # also valid typenames. So, if a struct with the name :Foo is created, then
463
+ # you can then use it in an encoding, like "bar: Foo [8]" to declare a member
464
+ # that is 8 Foo structs long.
465
+ #
466
+ # Structs, by default, are aligned to their largest member alignemnt. So, if
467
+ # a struct has four members with alignments of 8, 16, 32, and 4, the struct's
468
+ # overall alignment is 32 bytes.
469
+ #
470
+ # Endianness is not handled by structs and must be checked for and handled in
471
+ # your code.
472
+ #
473
+ #
474
+ # ### Struct Classes
475
+ #
476
+ # Struct classes all declare methods for reading and writing their members.
477
+ # Member access is provided via `#get_<member_name>(index = 0)` and modifying
478
+ # members is through `#set_<member_name>(value, index = 0)`. These are also
479
+ # aliased as `#<member_name>` and `#<member_name>=` for convenience,
480
+ # particularly with members whose lengths are 1.
481
+ #
482
+ # Struct members will always return a new instance of the member type that
483
+ # wraps the member at its address -- a copy of the memory at that location is
484
+ # not created.
485
+ #
486
+ # All struct classes also have an Array class (as `StructKlass::Array`) as
487
+ # well that provides simple access to resizable arrays. Tehse provide both
488
+ # `fetch(index)` and `store(index, value)` methods, both aliased to `[]` and
489
+ # `[]=` respectively.
490
+ #
491
+ def self.new(*args)
492
+ encoding, klass_name = case args.length
493
+ when 1 then args
494
+ when 2 then args.reverse
495
+ else
496
+ raise ArgumentError, "Invalid arguments to CStruct::new"
497
+ end
498
+
499
+ klass_name = klass_name.intern if klass_name
500
+
501
+ members = []
502
+
503
+ encoding.scan(ENCODING_REGEX) do
504
+ |match|
505
+ name = match[0].intern
506
+ type = match[1].intern
507
+ type = LONG_NAMES[type] if LONG_NAMES.include?(type)
508
+ length = (match[3] || 1).to_i
509
+ align = (match[5] || ALIGNMENTS[type] || 1).to_i
510
+ offset = 0
511
+
512
+ last_type = members.last
513
+ if last_type
514
+ offset += Memory.align_size(last_type.offset + last_type.size, align)
515
+ end
516
+
517
+ members << StructMemberInfo[name, type, SIZES[type] * length, length, align, offset].freeze
518
+ end
519
+
520
+ raise "No valid members found in encoding" if members.empty?
521
+
522
+ alignment = members.map { |member| member.alignment }.max { |lhs, rhs| lhs <=> rhs }
523
+ size = members.last.size + members.last.offset
524
+ aligned_size = Memory.align_size(size, alignment)
525
+
526
+ members.freeze
527
+
528
+ klass = Class.new(StructBase) do |struct_klass|
529
+ const_set(:ENCODING, String.new(encoding).freeze)
530
+ const_set(:MEMBERS, members)
531
+ const_set(:SIZE, size)
532
+ const_set(:ALIGNED_SIZE, aligned_size)
533
+ const_set(:ALIGNMENT, alignment)
534
+ const_set(:MEMBERS_HASH, members.reduce({}) { |offs, member| offs[member.name] = member ; offs })
535
+
536
+ def self.new(&block)
537
+ inst = __malloc__(self::SIZE, self::ALIGNMENT)
538
+ yield(inst) if block_given?
539
+ inst
540
+ end
541
+
542
+ self::MEMBERS.each do
543
+ |member|
544
+
545
+ name = member.name
546
+ index_range = (0...member.length)
547
+ type_name = member.type
548
+ type_size = ::Snow::CStruct::SIZES[member.type]
549
+ offset = member.offset
550
+ getter = :"get_#{type_name}"
551
+ setter = :"set_#{type_name}"
552
+ get_name = :"get_#{name}"
553
+ set_name = :"set_#{name}"
554
+
555
+ define_method(get_name) do |index = 0|
556
+ if index === index_range
557
+ raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
558
+ end
559
+ off = offset + index * type_size
560
+ __send__(getter, off)
561
+ end # get_name
562
+
563
+
564
+ define_method(set_name) do |value, index = 0|
565
+ if index === index_range
566
+ raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
567
+ end
568
+ off = offset + index * type_size
569
+ __send__(setter, off, value)
570
+ value
571
+ end # set_name
572
+
573
+
574
+ alias_method :"#{name}", get_name
575
+ alias_method :"#{name}=", set_name
576
+
577
+ end # define member get / set methods
578
+
579
+
580
+ # Array inner class (not a subclass of StructBase because it has no members itself)
581
+ const_set(:Array, Class.new(Memory) do |array_klass|
582
+
583
+ const_set(:BASE, struct_klass)
584
+
585
+ # The length of the array.
586
+ attr_reader :length
587
+
588
+
589
+ include Enumerable
590
+
591
+
592
+ def self.wrap(address, length_in_elements) # :nodoc:
593
+ __wrap__(address, length_in_elements * self::BASE::SIZE)
594
+ end
595
+
596
+
597
+ def self.new(length) # :nodoc:
598
+ length = length.to_i
599
+ raise ArgumentError, "Length must be greater than zero" if length < 1
600
+ inst = __malloc__(length * self::BASE::SIZE, self::BASE::ALIGNMENT)
601
+ inst.instance_variable_set(:@length, length)
602
+ inst.instance_variable_set(:@__cache__, nil)
603
+ inst
604
+ end
605
+
606
+
607
+ def resize!(new_length) # :nodoc:
608
+ raise ArgumentError, "Length must be greater than zero" if new_length < 1
609
+ realloc!(new_length * self.class::BASE::SIZE, self.class::BASE::ALIGNMENT)
610
+ @length = new_length
611
+ __free_cache__
612
+ self
613
+ end
614
+
615
+
616
+ def each(&block) # :nodoc:
617
+ return to_enum(:each) unless block_given?
618
+ (0 ... self.length).each { |index| yield fetch(index) }
619
+ self
620
+ end
621
+
622
+
623
+ def map(&block) # :nodoc:
624
+ return to_enum(:map) unless block_given?
625
+ self.dup.map!(&block)
626
+ end
627
+
628
+
629
+ def map!(&block) # :nodoc:
630
+ return to_enum(:map!) unless block_given?
631
+ (0 ... self.length).each { |index| store(index, yield(fetch(index))) }
632
+ self
633
+ end
634
+
635
+
636
+ def to_a # :nodoc:
637
+ (0 ... self.length).map { |index| fetch(index) }
638
+ end
639
+
640
+
641
+ def fetch(index) # :nodoc:
642
+ raise RuntimeError, "Attempt to access deallocated array" if @length == 0
643
+ raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
644
+ __build_cache__ if ! @__cache__
645
+ @__cache__[index]
646
+ end
647
+ alias_method :[], :fetch
648
+
649
+
650
+ #
651
+ # You can use this to assign _any_ Data subclass to an array value, but
652
+ # keep in mind that the data assigned MUST -- again, MUST -- be at least
653
+ # as large as the array's base struct type in bytes or the assigned
654
+ # data object MUST respond to a bytesize message to get its size in
655
+ # bytes.
656
+ #
657
+ def store(index, data) # :nodoc:
658
+ raise RuntimeError, "Attempt to access deallocated array" if @length == 0
659
+ raise TypeError, "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
660
+ raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
661
+ @__cache__[index].copy!(data)
662
+ data
663
+ end
664
+ alias_method :[]=, :store
665
+
666
+
667
+
668
+ def free! # :nodoc:
669
+ __free_cache__
670
+ @length = 0
671
+ super
672
+ end
673
+
674
+
675
+ private
676
+
677
+ def __free_cache__ # :nodoc:
678
+ if @__cache__
679
+ @__cache__.each { |entry|
680
+ entry.free!
681
+ entry.remove_instance_variable(:@__base_memory__)
682
+ } # zeroes address, making it NULL
683
+ @__cache__ = nil
684
+ end
685
+ end
686
+
687
+
688
+ def __build_cache__ # :nodoc:
689
+ addr = self.address
690
+ @__cache__ = (0...length).map { |index|
691
+ wrapper = self.class::BASE.__wrap__(addr + index * self.class::BASE::SIZE, self.class::BASE::SIZE)
692
+ # Make sure the wrapped object keeps the memory from being collected while it's in use
693
+ wrapper.instance_variable_set(:@__base_memory__, self)
694
+ wrapper
695
+ }
696
+ end
697
+
698
+
699
+ class <<self # :nodoc: all
700
+ alias_method :[], :new
701
+ end
702
+
703
+ end)
704
+
705
+ def self.[](length) # :nodoc:
706
+ self::Array.new(length)
707
+ end
708
+
709
+ end
710
+
711
+ if klass_name
712
+ const_set(klass_name, klass)
713
+ add_type(klass_name, klass)
714
+ end
715
+
716
+ klass
717
+ end
718
+
719
+ class <<self ; alias_method :[], :new ; end
720
+
721
+ end # class CStruct
722
+
723
+
724
+ end # module Snow