hakuban 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: []