hakuban 0.5.3 → 0.6.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.
@@ -7,6 +7,7 @@ require_relative './ffi.rb'
7
7
  #TODO: privative methods
8
8
  #TODO: error classes
9
9
 
10
+ #AutoPointers use proc-wrapped calls because they segfault sometimes when used with FFI::method(:xxx) (why?)
10
11
 
11
12
  module Hakuban
12
13
 
@@ -50,7 +51,7 @@ module Hakuban
50
51
 
51
52
  attr_reader :local_node_pointer #todo: hide
52
53
 
53
- def initialize(name=nil)
54
+ def initialize(name: nil)
54
55
  @default_serializer = lambda { |data_type, data|
55
56
  [["JSON"]+data_type, JSON.dump(data)]
56
57
  }
@@ -157,15 +158,16 @@ module Hakuban
157
158
  attr_reader :tags, :json
158
159
 
159
160
  def initialize(tags,json)
160
- @tags, @json = Set.new(tags), json
161
+ @tags = Set.new(tags.map { |tag| tag.kind_of?(TagDescriptor) ? tag : TagDescriptor.new(tag) })
162
+ @json = json
161
163
  end
162
164
 
163
165
  def to_ffi
164
- FFI::FFIObjectDescriptor.construct(@tags, @json)
166
+ FFI::FFIObjectDescriptor.construct(@tags.map(&:json), @json)
165
167
  end
166
168
 
167
169
  def self.from_ffi(ffi)
168
- ObjectDescriptor.new(ffi.tags, ffi.json)
170
+ ObjectDescriptor.new(ffi.tags.map{ |tag| TagDescriptor.new(tag) }, ffi.json)
169
171
  end
170
172
 
171
173
  def ==(other)
@@ -213,38 +215,35 @@ module Hakuban
213
215
  end
214
216
 
215
217
 
216
- class EventQueue
217
-
218
- def initialize(queue, prefix, register_method, pointer, unregister_method)
219
- @queue = queue
220
- @callback = EventQueue.generate_callback(@queue, prefix)
221
- @registered_callback_pointer = ::FFI::AutoPointer.new(
222
- register_method.call(pointer, @callback, ::FFI::Pointer::NULL),
223
- EventQueue.generate_unregister(unregister_method, @callback)
224
- )
225
- #puts "Allocated callback pointer: #{@registered_callback_pointer}"
226
- #puts "Allocated callback queue: #{@events}"
227
- end
218
+ class ObjectDescriptorEvents
219
+ def initialize(pointer)
220
+ @events_pointer = ::FFI::AutoPointer.new(pointer, proc { |ptr| FFI.hakuban_object_descriptor_events_return(ptr) })
221
+ end
228
222
 
229
- def self.generate_callback(queue, prefix)
230
- proc { |_userdata, ffi_descriptor, ffi_action|
231
- #puts "Callback for queue: #{queue}"
223
+ # WARNING: this callback may be run from a separate, non-ruby, thread
224
+ def callback_register(&callback)
225
+ ffi_callback = proc { |_userdata, ffi_descriptor, ffi_action|
232
226
  action = Hakuban::action_int_to_symbol(ffi_action)
233
227
  descriptor = ObjectDescriptor.from_ffi(ffi_descriptor)
234
- queue << prefix + [action, descriptor]
228
+ callback.call(descriptor, action)
235
229
  }
230
+ @callback_pointer = ::FFI::AutoPointer.new(
231
+ FFI::hakuban_object_descriptor_events_callback_register(@events_pointer, ffi_callback, ::FFI::Pointer::NULL),
232
+ proc { |ptr| FFI::hakuban_object_descriptor_events_callback_unregister(ptr) }
233
+ )
236
234
  end
237
235
 
238
- def self.generate_unregister(unregister_method, callback)
239
- proc { |pointer|
240
- #puts "Dropping callback pointer: #{pointer}"
241
- callback # probably not needed, but I want to be sure this is captured
242
- unregister_method.call(pointer)
243
- }
236
+ def callback_unregister
237
+ @callback_pointer.free
238
+ @callback_pointer = nil
244
239
  end
245
240
 
246
- end
241
+ def drop
242
+ @events_pointer.free
243
+ @events_pointer = nil
244
+ end
247
245
 
246
+ end
248
247
 
249
248
  class ObjectObserve
250
249
 
@@ -254,27 +253,21 @@ module Hakuban
254
253
  @local_node, @descriptor, @deserializer = local_node, descriptor, deserializer
255
254
  result = FFI::hakuban_object_observe_new(@local_node.local_node_pointer, descriptor.to_ffi)
256
255
  Hakuban::raise_if_error(result)
257
- @object_observe_pointer = ::FFI::AutoPointer.new(result[:object_observe_pointer], FFI::method(:hakuban_object_observe_drop))
258
- @queues = {}
256
+ @object_observe_pointer = ::FFI::AutoPointer.new(result[:object_observe_pointer], proc { |ptr| FFI::hakuban_object_observe_drop(ptr) })
259
257
  end
260
258
 
261
259
  def object_state
262
260
  raise "Attempt to use after 'drop'" if not @object_observe_pointer
263
- ObjectObserveState.new(FFI::hakuban_object_observe_state_borrow(@object_observe_pointer), @deserializer)
264
- end
265
-
266
- def send_events_to(queue, *prefix)
267
- @queues[queue] = EventQueue.new(
268
- queue,
269
- prefix,
270
- FFI::method(:hakuban_object_observe_changes_callback_register),
271
- @object_observe_pointer,
272
- FFI::method(:hakuban_object_callback_unregister)
273
- )
261
+ if (state_ptr = FFI::hakuban_object_observe_state_borrow(@object_observe_pointer)).null?
262
+ nil
263
+ else
264
+ ObjectObserveState.new(state_ptr, @deserializer)
265
+ end
274
266
  end
275
267
 
276
- def stop_sending_objects_events_to(queue)
277
- @queues.delete(queue)
268
+ def events
269
+ raise "Attempt to use after 'drop'" if not @object_observe_pointer
270
+ ObjectDescriptorEvents.new(FFI::hakuban_object_observe_events_get(@object_observe_pointer))
278
271
  end
279
272
 
280
273
  def inspect
@@ -284,13 +277,10 @@ module Hakuban
284
277
  def drop
285
278
  @object_observe_pointer.free
286
279
  @object_observe_pointer = nil
287
- @queues.clear
288
280
  end
289
281
 
290
- def manage(manager=nil)
291
- manager ||= ObjectManager.new
292
- manager.contract = self
293
- manager
282
+ def dropped?
283
+ @object_observe_pointer.nil?
294
284
  end
295
285
 
296
286
  end
@@ -305,34 +295,33 @@ module Hakuban
305
295
  result = FFI::hakuban_object_expose_new(@local_node.local_node_pointer, descriptor.to_ffi)
306
296
  Hakuban::raise_if_error(result)
307
297
  @object_expose_pointer = ::FFI::AutoPointer.new(result[:object_expose_pointer], FFI::method(:hakuban_object_expose_drop))
308
- @queues = {}
309
298
  end
310
299
 
311
- def set_object_state(version, data, data_type = [])
300
+ def set_object_state(version, data, data_type: [], assignment: 0)
312
301
  raise "Attempt to use after 'drop'" if not @object_expose_pointer
313
302
  serialized_data_type, serialized_data = @serializer.call(data_type, data)
314
- result = FFI::hakuban_object_expose_state(@object_expose_pointer, FFI::FFIObjectExposeState.construct(version, serialized_data_type, serialized_data))
303
+ result = FFI::hakuban_object_expose_state(@object_expose_pointer, FFI::FFIObjectExposeState.construct(version, serialized_data_type, serialized_data), assignment)
315
304
  Hakuban::raise_if_error(result)
316
305
  result[:changed] == 1
317
306
  end
318
307
 
319
- def assigned
308
+ def assignment
320
309
  raise "Attempt to use after 'drop'" if not @object_expose_pointer
321
- FFI::hakuban_object_expose_assigned(@object_expose_pointer) == 1
310
+ FFI::hakuban_object_expose_assignment(@object_expose_pointer)
322
311
  end
323
312
 
324
- def send_events_to(queue, *prefix)
325
- @queues[queue] = EventQueue.new(
326
- queue,
327
- prefix,
328
- FFI::method(:hakuban_object_expose_changes_callback_register),
329
- @object_expose_pointer,
330
- FFI::method(:hakuban_object_callback_unregister)
331
- )
313
+ def assigned?
314
+ assignment > 0
315
+ end
316
+
317
+ def desynchronize(assignment)
318
+ raise "Attempt to use after 'drop'" if not @object_expose_pointer
319
+ FFI::hakuban_object_expose_desynchronize(@object_expose_pointer, assignment)
332
320
  end
333
321
 
334
- def stop_sending_objects_events_to(queue)
335
- @queues.delete(queue)
322
+ def events
323
+ raise "Attempt to use after 'drop'" if not @object_expose_pointer
324
+ ObjectDescriptorEvents.new(FFI::hakuban_object_expose_events_get(@object_expose_pointer))
336
325
  end
337
326
 
338
327
  def inspect
@@ -342,13 +331,10 @@ module Hakuban
342
331
  def drop
343
332
  @object_expose_pointer.free
344
333
  @object_expose_pointer = nil
345
- @queues.clear
346
334
  end
347
335
 
348
- def manage(manager=nil)
349
- manager ||= ObjectManager.new
350
- manager.contract = self
351
- manager
336
+ def dropped?
337
+ @object_expose_pointer.nil?
352
338
  end
353
339
 
354
340
  end
@@ -356,25 +342,41 @@ module Hakuban
356
342
 
357
343
  class ObjectObserveState
358
344
 
359
- attr_reader :data, :data_type, :data_version, :synchronized
360
-
361
- def initialize(raw_state, deserializer)
362
- @raw_state = raw_state
363
- @synchronized = (raw_state[:synchronized] == 1)
364
- @data_version = raw_state[:version].read_array_of_type(::FFI::TYPE_INT64, :read_int64, raw_state[:version_length]) if not raw_state[:version].null?
365
- serialized_data_type = raw_state[:data_type].read_array_of_pointer(raw_state[:data_type_length]).map { |string| string.read_string() }
366
- @data_type, @data = deserializer.call(serialized_data_type, raw_state[:raw].read_string_length(raw_state[:raw_length])) if not raw_state[:raw].null?
367
- ObjectSpace.define_finalizer(self, ObjectObserveState.finalize(@raw_state))
345
+ def initialize(state, deserializer)
346
+ @state, @deserializer = state, deserializer
347
+ ObjectSpace.define_finalizer(self, ObjectObserveState.finalize(@state))
368
348
  end
369
349
 
370
- def ObjectObserveState.finalize(raw_state)
350
+ def ObjectObserveState.finalize(state)
371
351
  proc { |_|
372
- FFI::hakuban_object_observe_state_return(raw_state)
352
+ FFI::hakuban_object_observe_state_return(state)
373
353
  }
374
354
  end
375
355
 
376
356
  def version
377
- @data_version
357
+ return @data_version if @data_version
358
+ ffi_version = FFI::hakuban_object_observe_state_get_data_version(@state)
359
+ @data_version = ffi_version[:version_elements].read_array_of_type(::FFI::TYPE_INT64, :read_int64, ffi_version[:version_length])
360
+ end
361
+
362
+ def data
363
+ return @data if @data
364
+ ffi_data = FFI::hakuban_object_observe_state_get_data(@state)
365
+ ffi_data_type = FFI::hakuban_object_observe_state_get_data_type(@state)
366
+ serialized_data_type = ffi_data_type[:type_elements].read_array_of_pointer(ffi_data_type[:type_length]).map { |string| string.read_string() }
367
+ @data_type, @data = @deserializer.call(serialized_data_type, ffi_data[:data_bytes].read_string_length(ffi_data[:data_length]))
368
+ @data
369
+ end
370
+
371
+ def data_type
372
+ return @data_type if @data_type
373
+ data
374
+ @data_type
375
+ end
376
+
377
+ def synchronized
378
+ return @synchronized if @synchronized
379
+ @synchronized = FFI::hakuban_object_observe_state_get_synchronized(@state)
378
380
  end
379
381
 
380
382
  def inspect
@@ -393,7 +395,6 @@ module Hakuban
393
395
  result = FFI::hakuban_tag_observe_new(@local_node.local_node_pointer, @descriptor.to_ffi)
394
396
  Hakuban::raise_if_error(result)
395
397
  @tag_observe_pointer = ::FFI::AutoPointer.new(result[:tag_observe_pointer], FFI::method(:hakuban_tag_observe_drop))
396
- @queues = {}
397
398
  end
398
399
 
399
400
  def object_descriptors
@@ -412,34 +413,18 @@ module Hakuban
412
413
  ObjectObserveState.new(result[:state], @deserializer)
413
414
  end
414
415
 
415
- def send_events_to(*params)
416
- self.send_objects_events_to(*params)
417
- end
418
-
419
- def send_objects_events_to(queue, *prefix)
420
- @queues[queue] = EventQueue.new(
421
- queue,
422
- prefix,
423
- FFI::method(:hakuban_tag_observe_objects_changes_callback_register),
424
- @tag_observe_pointer,
425
- FFI::method(:hakuban_object_callback_unregister)
426
- )
427
- end
428
-
429
- def stop_sending_objects_events_to(queue)
430
- @queues.delete(queue)
416
+ def events
417
+ raise "Attempt to use after 'drop'" if not @tag_observe_pointer
418
+ ObjectDescriptorEvents.new(FFI::hakuban_tag_observe_events_get(@tag_observe_pointer))
431
419
  end
432
420
 
433
421
  def drop
434
422
  @tag_observe_pointer.free
435
423
  @tag_observe_pointer = nil
436
- @queues.clear
437
424
  end
438
425
 
439
- def manage(manager=nil)
440
- manager ||= ObjectManager.new
441
- manager.contract = self
442
- manager
426
+ def dropped?
427
+ @tag_observe_pointer.nil?
443
428
  end
444
429
 
445
430
  end
@@ -454,7 +439,6 @@ module Hakuban
454
439
  result = FFI::hakuban_tag_expose_new(@local_node.local_node_pointer, descriptor.to_ffi)
455
440
  Hakuban::raise_if_error(result)
456
441
  @tag_expose_pointer = ::FFI::AutoPointer.new(result[:tag_expose_pointer], FFI::method(:hakuban_tag_expose_drop))
457
- @queues = {}
458
442
  end
459
443
 
460
444
  def object_descriptors
@@ -465,42 +449,41 @@ module Hakuban
465
449
  ret
466
450
  end
467
451
 
468
- def set_object_state(object_descriptor, version, data, data_type = [])
452
+ def set_object_state(object_descriptor, version, data, data_type: [], assignment: 0)
469
453
  raise "Attempt to use after 'drop'" if not @tag_expose_pointer
454
+ raise if not version #TODO: replace this by type check
470
455
  serialized_data_type, serialized_data = @serializer.call(data_type, data)
471
- result = FFI::hakuban_tag_expose_object_state(@tag_expose_pointer, object_descriptor.to_ffi, FFI::FFIObjectExposeState.construct(version, serialized_data_type, serialized_data))
456
+ result = FFI::hakuban_tag_expose_object_state(@tag_expose_pointer, object_descriptor.to_ffi, FFI::FFIObjectExposeState.construct(version, serialized_data_type, serialized_data), assignment)
472
457
  Hakuban::raise_if_error(result)
473
458
  result[:changed] == 1
474
459
  end
475
460
 
476
- def send_events_to(*params)
477
- self.send_objects_events_to(*params)
461
+ def assignment(object_descriptor)
462
+ raise "Attempt to use after 'drop'" if not @tag_expose_pointer
463
+ FFI::hakuban_tag_expose_object_assignment(@tag_expose_pointer, object_descriptor.to_ffi)
478
464
  end
479
465
 
480
- def send_objects_events_to(queue, *prefix)
481
- @queues[queue] = EventQueue.new(
482
- queue,
483
- prefix,
484
- FFI::method(:hakuban_tag_expose_objects_changes_callback_register),
485
- @tag_expose_pointer,
486
- FFI::method(:hakuban_object_callback_unregister)
487
- )
466
+ def assigned?(object_descriptor)
467
+ assignment(object_descriptor) > 0
468
+ end
469
+
470
+ def desynchronize(object_descriptor, assignment)
471
+ raise "Attempt to use after 'drop'" if not @tag_expose_pointer
472
+ FFI::hakuban_tag_expose_object_desynchronize(@tag_expose_pointer, object_descriptor.to_ffi, assignment)
488
473
  end
489
474
 
490
- def stop_sending_objects_events_to(queue)
491
- @queues.delete(queue)
475
+ def events
476
+ raise "Attempt to use after 'drop'" if not @tag_expose_pointer
477
+ ObjectDescriptorEvents.new(FFI::hakuban_tag_expose_events_get(@tag_expose_pointer))
492
478
  end
493
479
 
494
480
  def drop
495
481
  @tag_expose_pointer.free
496
482
  @tag_expose_pointer = nil
497
- @queues.clear
498
483
  end
499
484
 
500
- def manage(manager=nil)
501
- manager ||= ObjectManager.new
502
- manager.contract = self
503
- manager
485
+ def dropped?
486
+ @tag_expose_pointer.nil?
504
487
  end
505
488
 
506
489
  end
@@ -544,150 +527,4 @@ module Hakuban
544
527
  end
545
528
 
546
529
 
547
- # convenience utils
548
-
549
- #TODO: replace this mess (<Hash, dynamic mixins) with some "deref"
550
- class ObjectManager < Hash
551
-
552
- module ObservedObjectManager
553
- def descriptor; self.values.first.descriptor; end
554
- def state; self.values.first.state; end
555
- def data; self.values.first.data; end
556
- def version; self.values.first.verison; end
557
- def synchronized; self.values.first.synchronized; end
558
- def construct_object(contract, descriptor); ObservedObject.new(contract, descriptor); end
559
- end
560
-
561
- module ExposedObjectManager
562
- def descriptor; self.values.first.descriptor; end
563
- def state=(params); self.values.first.state = params; end
564
- def data=(params); self.values.first.data = params; end
565
- def state_set(params); self.values.first.state_set(params); end
566
- def data_set(params); self.values.first.data_set(params); end
567
- def construct_object(contract, descriptor); ExposedObject.new(contract, descriptor); end
568
- end
569
-
570
- module ObservedTagManager
571
- def construct_object(contract, descriptor); ObservedObject.new(contract, descriptor); end
572
- end
573
-
574
- module ExposedTagManager
575
- def construct_object(contract, descriptor); ExposedObject.new(contract, descriptor); end
576
- end
577
-
578
- attr_reader :objects, :contract
579
-
580
- def contract=(contract)
581
- raise "You can't change contracts, sorry." if @contract
582
- @contract = contract
583
- case @contract.class.name
584
- when "Hakuban::ObjectObserve" then singleton_class.include(ObservedObjectManager)
585
- when "Hakuban::ObjectExpose" then singleton_class.include(ExposedObjectManager)
586
- when "Hakuban::TagObserve" then singleton_class.include(ObservedTagManager)
587
- when "Hakuban::TagExpose" then singleton_class.include(ExposedTagManager)
588
- else raise
589
- end
590
-
591
- @queue = Queue.new
592
- @contract.send_events_to(@queue, :object)
593
- if @contract.kind_of?(ObjectObserve) or @contract.kind_of?(ObjectExpose)
594
- event, param, descriptor = @queue.pop
595
- raise if event != :object or param != :insert or not descriptor
596
- self[descriptor] = self.construct_object(@contract, descriptor)
597
- end
598
-
599
- @thread = Thread.new {
600
- loop {
601
- event,param,descriptor = @queue.shift
602
- case [event, param]
603
- when [:control, :quit]
604
- break
605
- when [:object, :insert]
606
- self[descriptor] = construct_object(@contract, descriptor)
607
- object_insert(self[descriptor])
608
- when [:object, :change]
609
- object_change(self[descriptor])
610
- when [:object, :remove]
611
- object_remove(delete(descriptor))
612
- end
613
- }
614
- }
615
- end
616
-
617
-
618
- def object_insert(object)
619
- # noop default
620
- end
621
-
622
- def object_change(object)
623
- # noop default
624
- end
625
-
626
- def object_remove(object)
627
- # noop default
628
- end
629
-
630
-
631
- def drop
632
- @contract.stop_sending_objects_events_to(@queue)
633
- @queue.push [:control, :quit]
634
- @thread.join
635
- @queue, @contract, @thread, @objects = nil, nil, nil, {}
636
- end
637
-
638
-
639
- class ManagedObject < OpenStruct
640
- attr_reader :contract, :descriptor
641
- def initialize(contract, descriptor)
642
- @contract, @descriptor = contract, descriptor
643
- super()
644
- end
645
- end
646
-
647
- #TODO: remove the arity thing...
648
- class ObservedObject < ManagedObject
649
- def state
650
- if @contract.method(:object_state).arity == 1
651
- @contract.object_state(@descriptor)
652
- else
653
- @contract.object_state
654
- end
655
- end
656
- def data
657
- if @contract.method(:object_state).arity == 1
658
- @contract.object_state(@descriptor)&.data
659
- else
660
- @contract.object_state&.data
661
- end
662
- end
663
- end
664
-
665
- class ExposedObject < ManagedObject
666
- def state_set(state)
667
- if @contract.method(:set_object_state).arity == -4
668
- @contract.set_object_state(@descriptor, *state)
669
- else
670
- @contract.set_object_state(*state)
671
- end
672
- end
673
- def state=(state)
674
- state_set(state)
675
- end
676
- def data_set(value)
677
- timestamp = Time.new.to_f
678
- if @contract.method(:set_object_state).arity == -4
679
- @contract.set_object_state(@descriptor,[1, timestamp.floor, ((timestamp - timestamp.floor)*1000000000).floor, 0],value)
680
- else
681
- @contract.set_object_state([1, timestamp.floor, ((timestamp - timestamp.floor)*1000000000).floor, 0],value)
682
- end
683
- end
684
- def data=(value)
685
- data_set(value)
686
- end
687
- end
688
- end
689
-
690
-
691
-
692
-
693
530
  end