hakuban 0.5.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 79db90522bce42223139845bf81169a9e75a2997a90e71952b0b00d852272ff6
4
+ data.tar.gz: 35d50834b1a5a2626e9e3ad62c063b04acfc86569f887e57afe2578125d994cc
5
+ SHA512:
6
+ metadata.gz: 5d74f9761c28495c9d6caed241fb907147aff987d8c9d5526ded5d15f6e43ac781354baae9894fc74684ecad374646d8fde4cf90b26550a2e954f6bbf95f5ecb
7
+ data.tar.gz: 2c48edc2b71800bf19e3a1f267de234493e1e54aa6ae6fb4042ba1fc79f22a35aa28015e2d0b37f6ddb5c491817cf6698330b609dba96077e6d26ddcd441cb28
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /vendor/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 yunta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Hakuban
2
+
3
+ `TODO: DOCUMENT`
4
+
5
+ There is only auto-generated documentation here right now.
6
+
7
+ The original hakuban library is here: [https://gitlab.com/yunta/hakuban](https://gitlab.com/yunta/hakuban)
8
+
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'hakuban'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle install
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install hakuban
25
+
26
+ ## Usage
27
+
28
+ `TODO: Write usage instructions here`
29
+
30
+ ## Development
31
+
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
33
+
34
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "hakuban"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,46 @@
1
+ require 'hakuban'
2
+ require_relative './cpu-usage.rb'
3
+
4
+
5
+ class Utilization < Hakuban::ObjectManager::ManagedObject
6
+ include Hakuban::ObjectManager::ObservedObject
7
+ include Hakuban::ObjectManager::ExposedObject
8
+
9
+ attr_reader :id
10
+
11
+ def initialize(contract, descriptor)
12
+ super
13
+ @id = descriptor.json["id"]
14
+ end
15
+ end
16
+
17
+
18
+ id = "#{$$}@#{`hostname`.strip}"
19
+
20
+ hakuban = Hakuban::LocalNode.new
21
+ connector = Hakuban::WebsocketConnector.new("ws://localhost:3001").start(hakuban)
22
+ all_utilizations = hakuban.tag("utilizations").observe.manage(Utilization)
23
+ my_utilization = hakuban.object(["utilizations"],{id: id}).expose.manage(Utilization)
24
+
25
+ previous_cpu_usage_state = nil
26
+ loop {
27
+ previous_cpu_usage_state, cpu_usage = CPU::current_usage(previous_cpu_usage_state)
28
+ my_utilization.data = [cpu_usage]
29
+
30
+ utilizations = all_utilizations.objects.values.sort_by(&:id).map { |utilization|
31
+ [utilization.id, utilization.data&.first]
32
+ }.select { |id, utilization| utilization }
33
+ next sleep 1 if utilizations.empty?
34
+
35
+ widest_id_width = utilizations.transpose[0].map(&:size).max
36
+ utilizations.each.with_index { |(id, utilization), i|
37
+ bars = utilization.map { |percentage| " ▁▂▃▄▅▆▇█"[percentage*9/100] }.join("")
38
+ bg_color = [0,0,180 + (i % 2)*64]
39
+ fg_color = [255,255,0]
40
+ puts "%s -> \e[48;2;%i;%i;%im\e[38;2;%i;%i;%im%s\e[49m\e[39m"%([id.ljust(widest_id_width)]+bg_color+fg_color+[bars])
41
+ }
42
+
43
+ sleep 1
44
+ #TODO: make this more friendly, save state, etc.
45
+ print "\e[2J\e[f"
46
+ }
@@ -0,0 +1,26 @@
1
+ require 'hakuban'
2
+ require_relative './cpu-usage.rb'
3
+
4
+
5
+ id = "#{$$}@#{`hostname`.strip}"
6
+
7
+ hakuban = Hakuban::LocalNode.new
8
+ connector = Hakuban::WebsocketConnector.new("ws://localhost:3001").start(hakuban)
9
+ all_utilizations = hakuban.tag("utilizations").observe
10
+ my_utilization = hakuban.object(["utilizations"],{id: id}).expose
11
+
12
+ previous_cpu_usage_state = nil
13
+
14
+ loop {
15
+ previous_cpu_usage_state, cpu_usage = CPU::current_usage(previous_cpu_usage_state)
16
+ my_utilization.set_object_state([Time.new.to_f],[],[cpu_usage]) if cpu_usage
17
+
18
+ all_utilizations.object_descriptors.map { |descriptor|
19
+ if data = all_utilizations.object_state(descriptor).data
20
+ puts "#{descriptor.json["id"]} -> #{data.first}"
21
+ end
22
+ }
23
+
24
+ sleep 1
25
+ puts
26
+ }
@@ -0,0 +1,16 @@
1
+ module CPU
2
+
3
+ def self.current_usage(previous_cpu_usage_state)
4
+ current_uptime = open("/proc/uptime") { |file| file.read.split(" ")[0].to_f }
5
+ current_cpu_ticks = open("/proc/stat") { |file|
6
+ file.read.split("\n").select { |line| line.start_with?(/cpu[0-9]+/) }.map { |line| line.split(" ")[4].to_f }
7
+ }
8
+ cpu_usage = previous_cpu_usage_state&.dig(0)&.zip(current_cpu_ticks)&.map { |old_ticks, new_ticks|
9
+ [0,(100 - (new_ticks - old_ticks) / (current_uptime - previous_cpu_usage_state[1]))].max.floor
10
+ }
11
+ [[current_cpu_ticks, current_uptime], cpu_usage]
12
+ end
13
+
14
+ end
15
+
16
+
data/hakuban.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/hakuban/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "hakuban"
7
+ spec.version = Hakuban::VERSION
8
+ spec.authors = ["yunta"]
9
+ spec.email = ["maciej.blomberg@mikoton.com"]
10
+
11
+ spec.summary = "Ruby binding for Hakuban library"
12
+ spec.description = "Ruby binding for convenient data-object sharing library - Hakuban."
13
+ spec.homepage = "https://gitlab.com/yunta/hakuban-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
+
17
+ #spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
+
19
+ #spec.metadata["homepage_uri"] = spec.homepage
20
+ #spec.metadata["source_code_uri"] = "https://gitlab.com/yunta/hakuban-ruby"
21
+ #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ #spec.bindir = "exe"
29
+ #spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency "rspec", "~>3.10"
33
+ spec.add_dependency "ffi", "~>1.15"
34
+ spec.add_dependency "msgpack", "~>1.4"
35
+ spec.add_dependency "json", "~>2.5"
36
+ end
data/lib/hakuban.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hakuban/version"
4
+ require_relative "hakuban/hakuban"
5
+
6
+ module Hakuban
7
+ end
@@ -0,0 +1,152 @@
1
+ require 'json'
2
+ require 'ffi'
3
+ require 'msgpack'
4
+
5
+
6
+ # all functions which can trigger synchronuous callbacks, and those which wait on rust locks are "blocking: true" to avoid deadlock on GIL
7
+ module Hakuban::FFI
8
+
9
+ class FFILocalNodeNewResult < FFI::Struct
10
+ layout :error, :uint8, :local_node_pointer, :pointer
11
+ end
12
+
13
+ class FFIObjectObserveResult < FFI::Struct
14
+ layout :error, :uint8, :object_observe_pointer, :pointer
15
+ end
16
+
17
+ class FFIObjectExposeResult < FFI::Struct
18
+ layout :error, :uint8, :object_expose_pointer, :pointer
19
+ end
20
+
21
+ class FFITagObserveResult < FFI::Struct
22
+ layout :error, :uint8, :tag_observe_pointer, :pointer
23
+ end
24
+
25
+ class FFITagExposeResult < FFI::Struct
26
+ layout :error, :uint8, :tag_expose_pointer, :pointer
27
+ end
28
+
29
+ class FFIObjectObserveState < FFI::Struct
30
+ layout :synchronized, :uint8, :version_length, :size_t, :version, :pointer, :data_type_length, :size_t, :data_type, :pointer, :raw_length, :size_t, :raw, :pointer, :raw_arc, :pointer
31
+ end
32
+
33
+ class FFIObjectExposeState < FFI::Struct
34
+ layout :version_length, :size_t, :version, :pointer, :data_type_length, :size_t, :data_type, :pointer, :raw_length, :size_t, :raw, :pointer
35
+
36
+ def self.construct(version, data_type, data)
37
+ state = FFIObjectExposeState::new
38
+ state[:version_length] = version.size
39
+ state[:version] = FFI::MemoryPointer.new(:int64, version.size)
40
+ state[:version].write_array_of_int64(version)
41
+ state[:data_type_length] = data_type.size
42
+ state[:data_type] = FFI::MemoryPointer.new(:pointer, data_type.size)
43
+ state[:data_type].write_array_of_pointer(data_type.map {|string| FFI::MemoryPointer.from_string(string)})
44
+ state[:raw] = FFI::MemoryPointer.from_string(data)
45
+ state[:raw_length] = data.size
46
+ state
47
+ end
48
+ end
49
+
50
+ class FFIObjectExposeStateResult < FFI::Struct
51
+ layout :error, :uint8, :changed, :uint8
52
+ end
53
+
54
+ class FFITokioWebsocketConnectorNewResult < FFI::Struct
55
+ layout :error, :uint8, :websocket_connector_pointer, :pointer
56
+ end
57
+
58
+ class FFITagObserveObjectStateBorrowResult < FFI::Struct
59
+ layout :error, :uint8, :state, FFIObjectObserveState.by_value
60
+ end
61
+
62
+ class FFIObjectDescriptor < FFI::Struct
63
+ layout :tags_count, :size_t, :tags, :pointer, :json, :pointer
64
+
65
+ def tags
66
+ self[:tags].read_array_of_pointer(self[:tags_count]).map { |string| JSON.parse(string.read_string()) } # does this copy the string?
67
+ end
68
+
69
+ def json
70
+ JSON.parse(self[:json].read_string())
71
+ end
72
+
73
+ def self.construct(tags,json)
74
+ tags_strings = tags.map { |tag| JSON.dump(tag) }
75
+ tags_strings_array = FFI::MemoryPointer.new(:pointer, tags_strings.size)
76
+ tags_strings_array.write_array_of_pointer(tags_strings.map {|string| FFI::MemoryPointer.from_string(string)})
77
+ descriptor = FFIObjectDescriptor.new
78
+ descriptor[:tags_count] = tags.size
79
+ descriptor[:tags] = tags_strings_array
80
+ descriptor[:json] = FFI::MemoryPointer.from_string(JSON.dump(json))
81
+ descriptor
82
+ end
83
+
84
+ end
85
+
86
+ class FFITagDescriptor < FFI::Struct
87
+ layout :json, :pointer
88
+
89
+ def json
90
+ JSON.parse(self[:json].read_string())
91
+ end
92
+
93
+ def self.construct(json)
94
+ descriptor = FFITagDescriptor.new
95
+ descriptor[:json] = FFI::MemoryPointer.from_string(JSON.dump(json))
96
+ descriptor
97
+ end
98
+
99
+ end
100
+
101
+
102
+ class FFIObjectDescriptors < FFI::Struct
103
+ layout :count, :size_t, :descriptors, :pointer
104
+
105
+ def descriptors
106
+ self[:count].times.map { |i| FFIObjectDescriptor.new(self[:descriptors] + i * FFIObjectDescriptor.size) }
107
+ end
108
+ end
109
+
110
+
111
+ extend FFI::Library
112
+
113
+ ffi_lib 'hakuban'
114
+
115
+ callback :object_callback, [ :pointer, FFIObjectDescriptor.by_value, :uint8 ], :void
116
+ #callback :tag_callback, [ :pointer, FFITagDescriptor.by_value, :uint8 ], :void
117
+
118
+ attach_function :hakuban_local_node_new, [ :string ], FFILocalNodeNewResult.by_value
119
+ attach_function :hakuban_local_node_drop, [ :pointer ], :void
120
+
121
+ attach_function :hakuban_object_observe_new, [ :pointer, FFIObjectDescriptor.by_value ], FFIObjectObserveResult.by_value, blocking: true
122
+ attach_function :hakuban_object_observe_drop, [ :pointer ], :void, blocking: true
123
+ attach_function :hakuban_object_observe_state_borrow, [ :pointer ], FFIObjectObserveState.by_value, blocking: true
124
+ attach_function :hakuban_object_observe_changes_callback_register, [ :pointer, :object_callback, :pointer ], :pointer, blocking: true
125
+
126
+ attach_function :hakuban_object_expose_new, [ :pointer, FFIObjectDescriptor.by_value], FFIObjectExposeResult.by_value, blocking: true
127
+ attach_function :hakuban_object_expose_drop, [ :pointer ], :void, blocking: true
128
+ attach_function :hakuban_object_expose_state, [ :pointer, FFIObjectExposeState.by_value ], FFIObjectExposeStateResult.by_value, blocking: true
129
+ attach_function :hakuban_object_expose_assigned, [ :pointer ], :uint8, blocking: true
130
+ attach_function :hakuban_object_expose_changes_callback_register, [ :pointer, :object_callback, :pointer ], :pointer, blocking: true
131
+
132
+ attach_function :hakuban_tag_observe_new, [ :pointer, FFITagDescriptor.by_value ], FFITagObserveResult.by_value, blocking: true
133
+ attach_function :hakuban_tag_observe_drop, [ :pointer ], :void, blocking: true
134
+ attach_function :hakuban_tag_observe_object_descriptors_borrow, [ :pointer ], FFIObjectDescriptors.by_value, blocking: true
135
+ attach_function :hakuban_tag_observe_object_state_borrow, [ :pointer, FFIObjectDescriptor.by_value ], FFITagObserveObjectStateBorrowResult.by_value, blocking: true
136
+ attach_function :hakuban_tag_observe_objects_changes_callback_register, [ :pointer, :object_callback, :pointer ], :pointer, blocking: true
137
+
138
+ attach_function :hakuban_tag_expose_new, [ :pointer, FFITagDescriptor.by_value ], FFITagExposeResult.by_value, blocking: true
139
+ attach_function :hakuban_tag_expose_drop, [ :pointer ], :void, blocking: true
140
+ attach_function :hakuban_tag_expose_object_descriptors_borrow, [ :pointer ], FFIObjectDescriptors.by_value, blocking: true
141
+ attach_function :hakuban_tag_expose_object_state, [ :pointer, FFIObjectDescriptor.by_value, FFIObjectExposeState.by_value ], FFIObjectExposeStateResult.by_value, blocking: true
142
+ attach_function :hakuban_tag_expose_objects_changes_callback_register, [ :pointer, :object_callback, :pointer ], :pointer, blocking: true
143
+
144
+ attach_function :hakuban_object_observe_state_return, [ FFIObjectObserveState.by_value ], :void
145
+ attach_function :hakuban_object_descriptors_return, [ FFIObjectDescriptors.by_value ], :void
146
+ attach_function :hakuban_object_callback_unregister, [ :pointer ], :void, blocking: true
147
+
148
+ attach_function :hakuban_tokio_init_multi_thread, [ :size_t ], :pointer
149
+ attach_function :hakuban_tokio_websocket_connector_new, [ :pointer, :string ], FFITokioWebsocketConnectorNewResult.by_value
150
+ attach_function :hakuban_tokio_websocket_connector_start, [ :pointer, :pointer, :pointer ], :void
151
+
152
+ end
@@ -0,0 +1,555 @@
1
+ require 'set'
2
+
3
+ require_relative './ffi.rb'
4
+
5
+ #TODO: explicit drops?
6
+ #TODO: privative methods
7
+
8
+ module Hakuban
9
+
10
+ def self.raise_if_error(result)
11
+ case result[:error]
12
+ when 0 then true
13
+ when 1 then raise "Invalid string"
14
+ when 2 then raise "Invalid JSON"
15
+ when 3 then raise "Invalid URL"
16
+ when 4 then raise "Object Not Found"
17
+ else raise "Unknown error, sorry :("
18
+ end
19
+ end
20
+
21
+
22
+ def self.action_int_to_symbol(number)
23
+ case number
24
+ when 0 then :insert
25
+ when 1 then :change
26
+ when 2 then :remove
27
+ end
28
+ end
29
+
30
+
31
+ class LocalNode
32
+
33
+ #TODO: explicit drop
34
+
35
+ attr_reader :local_node_pointer #todo: hide
36
+
37
+ def initialize(name=nil)
38
+ result = FFI::hakuban_local_node_new(name || "local")
39
+ Hakuban::raise_if_error(result)
40
+ @local_node_pointer = ::FFI::AutoPointer.new(result[:local_node_pointer], FFI::method(:hakuban_local_node_drop))
41
+ end
42
+
43
+ def object(tags, descriptor)
44
+ #TODO: accept real descriptor too
45
+ ObjectBuilder.new(self, ObjectDescriptor.new(tags,descriptor))
46
+ end
47
+
48
+ def tag(descriptor)
49
+ #TODO: accept real descriptor too
50
+ TagBuilder.new(self, TagDescriptor.new(descriptor))
51
+ end
52
+
53
+ end
54
+
55
+
56
+ class ObjectBuilder
57
+
58
+ def initialize(store, descriptor)
59
+ @store, @descriptor = store, descriptor
60
+ end
61
+
62
+ def observe
63
+ ObjectObserve.new(@store, @descriptor)
64
+ end
65
+
66
+ def expose
67
+ ObjectExpose.new(@store, @descriptor)
68
+ end
69
+
70
+ end
71
+
72
+
73
+ class TagBuilder
74
+
75
+ def initialize(store, descriptor)
76
+ @store, @descriptor = store, descriptor
77
+ end
78
+
79
+ def observe
80
+ TagObserve.new(@store, @descriptor)
81
+ end
82
+
83
+ def expose
84
+ TagExpose.new(@store, @descriptor)
85
+ end
86
+
87
+ end
88
+
89
+
90
+ class ObjectDescriptor
91
+ attr_reader :tags, :json
92
+
93
+ def initialize(tags,json)
94
+ @tags, @json = Set.new(tags), json
95
+ end
96
+
97
+ def to_ffi
98
+ FFI::FFIObjectDescriptor.construct(@tags, @json)
99
+ end
100
+
101
+ def self.from_ffi(ffi)
102
+ ObjectDescriptor.new(ffi.tags, ffi.json)
103
+ end
104
+
105
+ def ==(other)
106
+ @tags == other.tags and @json == other.json
107
+ end
108
+
109
+ alias eql? ==
110
+
111
+ def hash
112
+ [@tags.hash, @json.hash].hash
113
+ end
114
+ end
115
+
116
+
117
+ class TagDescriptor
118
+ attr_reader :json
119
+
120
+ def initialize(json)
121
+ @json = json
122
+ end
123
+
124
+ def to_ffi
125
+ FFI::FFITagDescriptor.construct(@json)
126
+ end
127
+
128
+ def self.from_ffi(ffi)
129
+ TagDescriptor.new(ffi.json)
130
+ end
131
+
132
+ def ==(other)
133
+ @json == other.json
134
+ end
135
+
136
+ alias eql? ==
137
+
138
+ def hash
139
+ @json.hash
140
+ end
141
+
142
+ end
143
+
144
+
145
+ class EventQueue
146
+
147
+ def initialize(queue, prefix, register_method, pointer, unregister_method)
148
+ @queue = queue
149
+ @callback = EventQueue.generate_callback(@queue, prefix)
150
+ @registered_callback_pointer = ::FFI::AutoPointer.new(
151
+ register_method.call(pointer, @callback, ::FFI::Pointer::NULL),
152
+ EventQueue.generate_unregister(unregister_method, @callback)
153
+ )
154
+ #puts "Allocated callback pointer: #{@registered_callback_pointer}"
155
+ #puts "Allocated callback queue: #{@events}"
156
+ end
157
+
158
+ def self.generate_callback(queue, prefix)
159
+ proc { |_userdata, ffi_descriptor, ffi_action|
160
+ #puts "Callback for queue: #{queue}"
161
+ action = Hakuban::action_int_to_symbol(ffi_action)
162
+ descriptor = ObjectDescriptor.from_ffi(ffi_descriptor)
163
+ queue << prefix + [action, descriptor]
164
+ }
165
+ end
166
+
167
+ def self.generate_unregister(unregister_method, callback)
168
+ proc { |pointer|
169
+ #puts "Dropping callback pointer: #{pointer}"
170
+ callback # probably not needed, but I want to be sure this is captured
171
+ unregister_method.call(pointer)
172
+ }
173
+ end
174
+
175
+ end
176
+
177
+
178
+ class ObjectObserve
179
+
180
+ attr_reader :descriptor
181
+
182
+ def initialize(local_node, descriptor)
183
+ @local_node, @descriptor = local_node, descriptor
184
+ result = FFI::hakuban_object_observe_new(@local_node.local_node_pointer, descriptor.to_ffi)
185
+ Hakuban::raise_if_error(result)
186
+ @object_observe_pointer = ::FFI::AutoPointer.new(result[:object_observe_pointer], FFI::method(:hakuban_object_observe_drop))
187
+ @queues = {}
188
+ end
189
+
190
+ def object_state
191
+ raise "Attempt to use after 'drop'" if not @object_observe_pointer
192
+ ObjectObserveState.new(FFI::hakuban_object_observe_state_borrow(@object_observe_pointer))
193
+ end
194
+
195
+ def send_events_to(queue, *prefix)
196
+ @queues[queue] = EventQueue.new(
197
+ queue,
198
+ prefix,
199
+ FFI::method(:hakuban_object_observe_changes_callback_register),
200
+ @object_observe_pointer,
201
+ FFI::method(:hakuban_object_callback_unregister)
202
+ )
203
+ end
204
+
205
+ def stop_sending_objects_events_to(queue)
206
+ @queues.delete(queue)
207
+ end
208
+
209
+ def inspect
210
+ "#<ObjectObserve #{@descriptor}>"
211
+ end
212
+
213
+ def drop
214
+ @object_observe_pointer.free
215
+ @object_observe_pointer = nil
216
+ @queues.values.each { |pointer| pointer.free }
217
+ @queues.clear
218
+ end
219
+
220
+ def manage(object_class, *object_constructor_params)
221
+ object_class::new(self, @descriptor, *object_constructor_params)
222
+ end
223
+
224
+ end
225
+
226
+
227
+ class ObjectExpose
228
+
229
+ attr_reader :descriptor
230
+
231
+ def initialize(local_node, descriptor)
232
+ @local_node, @descriptor = local_node, descriptor
233
+ result = FFI::hakuban_object_expose_new(@local_node.local_node_pointer, descriptor.to_ffi)
234
+ Hakuban::raise_if_error(result)
235
+ @object_expose_pointer = ::FFI::AutoPointer.new(result[:object_expose_pointer], FFI::method(:hakuban_object_expose_drop))
236
+ @queues = {}
237
+ end
238
+
239
+ def set_object_state(version, data_type, data)
240
+ raise "Attempt to use after 'drop'" if not @object_expose_pointer
241
+ data_type = ["MessagePack"] + data_type
242
+ serialized = MessagePack.pack(data)
243
+ result = FFI::hakuban_object_expose_state(@object_expose_pointer, FFI::FFIObjectExposeState.construct(version, data_type, serialized))
244
+ Hakuban::raise_if_error(result)
245
+ result[:changed] == 1
246
+ end
247
+
248
+ def assigned
249
+ raise "Attempt to use after 'drop'" if not @object_expose_pointer
250
+ FFI::hakuban_object_expose_assigned(@object_expose_pointer) == 1
251
+ end
252
+
253
+ def send_events_to(queue, *prefix)
254
+ @queues[queue] = EventQueue.new(
255
+ queue,
256
+ prefix,
257
+ FFI::method(:hakuban_object_expose_changes_callback_register),
258
+ @object_expose_pointer,
259
+ FFI::method(:hakuban_object_callback_unregister)
260
+ )
261
+ end
262
+
263
+ def stop_sending_objects_events_to(queue)
264
+ @queues.delete(queue)
265
+ end
266
+
267
+ def drop
268
+ @object_expose_pointer.free
269
+ @object_expose_pointer = nil
270
+ @queues.values.each { |pointer| pointer.free }
271
+ @queues.clear
272
+ end
273
+
274
+ def manage(object_class, *object_constructor_params)
275
+ object_class::new(self, @descriptor, *object_constructor_params)
276
+ end
277
+
278
+ end
279
+
280
+
281
+ class ObjectObserveState
282
+
283
+ attr_reader :data, :data_type, :data_version
284
+
285
+ def initialize(raw_state)
286
+ @raw_state = raw_state
287
+ @synchronized = (raw_state[:synchronized] == 1)
288
+ @data_type = raw_state[:data_type].read_array_of_pointer(raw_state[:data_type_length]).map { |string| string.read_string() }
289
+ @data_version = raw_state[:version].read_array_of_type(::FFI::TYPE_INT64, :read_int64, raw_state[:version_length]) if not raw_state[:version].null?
290
+ @data = MessagePack.unpack(raw_state[:raw].read_string_length(raw_state[:raw_length])) if not raw_state[:raw].null?
291
+ ObjectSpace.define_finalizer(self, ObjectObserveState.finalize(@raw_state))
292
+ end
293
+
294
+ def ObjectObserveState.finalize(raw_state)
295
+ proc { |_|
296
+ FFI::hakuban_object_observe_state_return(raw_state)
297
+ }
298
+ end
299
+
300
+ def version
301
+ @data_version
302
+ end
303
+
304
+ def inspect
305
+ "#<ObjectObserveState version=#{@data_version}>"
306
+ end
307
+
308
+ end
309
+
310
+
311
+ class TagObserve
312
+
313
+ attr_reader :descriptor
314
+
315
+ def initialize(local_node, descriptor)
316
+ @local_node, @descriptor = local_node, descriptor
317
+ result = FFI::hakuban_tag_observe_new(@local_node.local_node_pointer, @descriptor.to_ffi)
318
+ Hakuban::raise_if_error(result)
319
+ @tag_observe_pointer = ::FFI::AutoPointer.new(result[:tag_observe_pointer], FFI::method(:hakuban_tag_observe_drop))
320
+ @queues = {}
321
+ end
322
+
323
+ def object_descriptors
324
+ raise "Attempt to use after 'drop'" if not @tag_observe_pointer
325
+ result = FFI::hakuban_tag_observe_object_descriptors_borrow(@tag_observe_pointer)
326
+ ret = result.descriptors.map { |raw_descriptor| ObjectDescriptor::from_ffi(raw_descriptor) }
327
+ FFI::hakuban_object_descriptors_return(result)
328
+ ret
329
+ end
330
+
331
+ def object_state(object_descriptor)
332
+ raise "Attempt to use after 'drop'" if not @tag_observe_pointer
333
+ result = FFI::hakuban_tag_observe_object_state_borrow(@tag_observe_pointer, object_descriptor.to_ffi)
334
+ return nil if result[:error] == 4
335
+ Hakuban::raise_if_error(result)
336
+ ObjectObserveState.new(result[:state])
337
+ end
338
+
339
+ def send_events_to(*params)
340
+ self.send_objects_events_to(*params)
341
+ end
342
+
343
+ def send_objects_events_to(queue, *prefix)
344
+ @queues[queue] = EventQueue.new(
345
+ queue,
346
+ prefix,
347
+ FFI::method(:hakuban_tag_observe_objects_changes_callback_register),
348
+ @tag_observe_pointer,
349
+ FFI::method(:hakuban_object_callback_unregister)
350
+ )
351
+ end
352
+
353
+ def stop_sending_objects_events_to(queue)
354
+ @queues.delete(queue)
355
+ end
356
+
357
+ def drop
358
+ @tag_observe_pointer.free
359
+ @tag_observe_pointer = nil
360
+ @queues.values.each { |pointer| pointer.free }
361
+ @queues.clear
362
+ end
363
+
364
+ def manage(object_class, *object_constructor_params)
365
+ ObjectManager::new(self, object_class, *object_constructor_params)
366
+ end
367
+
368
+ end
369
+
370
+
371
+ class TagExpose
372
+
373
+ attr_reader :descriptor
374
+
375
+ def initialize(local_node, descriptor)
376
+ @local_node, @descriptor = local_node, descriptor
377
+ result = FFI::hakuban_tag_expose_new(@local_node.local_node_pointer, descriptor.to_ffi)
378
+ Hakuban::raise_if_error(result)
379
+ @tag_expose_pointer = ::FFI::AutoPointer.new(result[:tag_expose_pointer], FFI::method(:hakuban_tag_expose_drop))
380
+ @queues = {}
381
+ end
382
+
383
+ def object_descriptors
384
+ raise "Attempt to use after 'drop'" if not @tag_expose_pointer
385
+ result = FFI::hakuban_tag_expose_object_descriptors_borrow(@tag_expose_pointer)
386
+ ret = result.descriptors.map { |raw_descriptor| ObjectDescriptor::from_ffi(raw_descriptor) }
387
+ FFI::hakuban_object_descriptors_return(result)
388
+ ret
389
+ end
390
+
391
+ def set_object_state(object_descriptor, version, data_type, data)
392
+ raise "Attempt to use after 'drop'" if not @tag_expose_pointer
393
+ data_type = ["MessagePack"] + data_type
394
+ serialized = MessagePack.pack(data)
395
+ result = FFI::hakuban_tag_expose_object_state(@tag_expose_pointer, object_descriptor.to_ffi, FFI::FFIObjectExposeState.construct(version, data_type, serialized))
396
+ Hakuban::raise_if_error(result)
397
+ result[:changed] == 1
398
+ end
399
+
400
+ def send_events_to(*params)
401
+ self.send_objects_events_to(*params)
402
+ end
403
+
404
+ def send_objects_events_to(queue, *prefix)
405
+ @queues[queue] = EventQueue.new(
406
+ queue,
407
+ prefix,
408
+ FFI::method(:hakuban_tag_expose_objects_changes_callback_register),
409
+ @tag_expose_pointer,
410
+ FFI::method(:hakuban_object_callback_unregister)
411
+ )
412
+ end
413
+
414
+ def stop_sending_objects_events_to(queue)
415
+ @queues.delete(queue)
416
+ end
417
+
418
+ def drop
419
+ @tag_expose_pointer.free
420
+ @tag_expose_pointer = nil
421
+ @queues.values.each { |pointer| pointer.free }
422
+ @queues.clear
423
+ end
424
+
425
+ def manage(object_class, *object_constructor_params)
426
+ ObjectManager::new(self, object_class, *object_constructor_params)
427
+ end
428
+
429
+ end
430
+
431
+
432
+
433
+ class Tokio
434
+
435
+ @@tokio_pointer = nil
436
+
437
+ def Tokio.init(workers_count=0)
438
+ @@tokio_pointer ||= FFI::hakuban_tokio_init_multi_thread(0)
439
+ end
440
+
441
+ def Tokio.pointer
442
+ Tokio.init if not @@tokio_pointer
443
+ @@tokio_pointer
444
+ end
445
+
446
+ #TODO: drop?
447
+
448
+ end
449
+
450
+
451
+ class WebsocketConnector
452
+
453
+ #TODO: drop
454
+
455
+ def initialize(url)
456
+ result = FFI::hakuban_tokio_websocket_connector_new(Tokio.pointer, url)
457
+ Hakuban::raise_if_error(result)
458
+ @websocket_connector_pointer = result[:websocket_connector_pointer]
459
+ end
460
+
461
+ def start(local_node)
462
+ @local_node = local_node
463
+ FFI::hakuban_tokio_websocket_connector_start(Tokio.pointer, @websocket_connector_pointer, @local_node.local_node_pointer)
464
+ self
465
+ end
466
+
467
+ end
468
+
469
+
470
+ # convenience utils
471
+
472
+ class ObjectManager
473
+
474
+ attr_reader :objects, :contract
475
+
476
+ def initialize(contract, object_class, *object_constructor_params)
477
+ @contract = contract
478
+ @objects = {}
479
+ @queue = Queue.new
480
+ contract.send_events_to(@queue, :object)
481
+ @thread = Thread.new {
482
+ loop {
483
+ event = @queue.shift
484
+ case event[0..1]
485
+ when [:control, :quit]
486
+ break
487
+ when [:object, :insert]
488
+ @objects[event[2]] = object_class.new(@contract, event[2], *object_constructor_params)
489
+ when [:object, :change]
490
+ @objects[event[2]].change if @objects[event[2]].respond_to?(:change)
491
+ when [:object, :remove]
492
+ raise if not deleted = @objects.delete(event[2])
493
+ deleted.drop if deleted.respond_to?(:drop)
494
+ end
495
+ }
496
+ }
497
+ end
498
+
499
+ def drop
500
+ @contract.stop_sending_objects_events_to(@queue)
501
+ @queue.push [:control, :quit]
502
+ @thread.join
503
+ @queue, @contract, @thread, @objects = nil, nil, nil, {}
504
+ end
505
+
506
+
507
+ class ManagedObject
508
+ attr_reader :contract, :descriptor
509
+ def initialize(contract, descriptor)
510
+ @contract, @descriptor = contract, descriptor
511
+ end
512
+ end
513
+
514
+
515
+ #TODO: fix the arity thing...
516
+ module ObservedObject
517
+ def state
518
+ if @contract.method(:object_state).arity == 1
519
+ @contract.object_state(@descriptor)
520
+ else
521
+ @contract.object_state
522
+ end
523
+ end
524
+ def data
525
+ if @contract.method(:object_state).arity == 1
526
+ @contract.object_state(@descriptor)&.data
527
+ else
528
+ @contract.object_state&.data
529
+ end
530
+ end
531
+ end
532
+
533
+ module ExposedObject
534
+ def state=(state)
535
+ if @contract.method(:set_object_state).arity == 4
536
+ @contract.set_object_state(@descriptor, *state)
537
+ else
538
+ @contract.set_object_state(*state)
539
+ end
540
+ end
541
+ def data=(value)
542
+ timestamp = Time.new.to_f.floor
543
+ if @contract.method(:set_object_state).arity == 4
544
+ @contract.set_object_state(@descriptor,[1, timestamp.floor, ((timestamp - timestamp.floor)*1000000000).floor, 0],[],value)
545
+ else
546
+ @contract.set_object_state([1, timestamp.floor, ((timestamp - timestamp.floor)*1000000000).floor, 0],[],value)
547
+ end
548
+ end
549
+ end
550
+ end
551
+
552
+
553
+
554
+
555
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hakuban
4
+ VERSION = "0.5.0"
5
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hakuban
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - yunta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ffi
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: msgpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.5'
69
+ description: Ruby binding for convenient data-object sharing library - Hakuban.
70
+ email:
71
+ - maciej.blomberg@mikoton.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - examples/all-top-managed.rb
85
+ - examples/all-top-simple.rb
86
+ - examples/cpu-usage.rb
87
+ - hakuban.gemspec
88
+ - lib/hakuban.rb
89
+ - lib/hakuban/ffi.rb
90
+ - lib/hakuban/hakuban.rb
91
+ - lib/hakuban/version.rb
92
+ homepage: https://gitlab.com/yunta/hakuban-ruby
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 2.4.0
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.2.15
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Ruby binding for Hakuban library
115
+ test_files: []