async-bus 0.1.0 → 0.2.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/bus/client.rb +68 -36
- data/lib/async/bus/controller.rb +58 -0
- data/lib/async/bus/protocol/connection.rb +166 -70
- data/lib/async/bus/protocol/invoke.rb +58 -0
- data/lib/async/bus/protocol/proxy.rb +37 -25
- data/lib/async/bus/protocol/release.rb +29 -0
- data/lib/async/bus/protocol/response.rb +39 -0
- data/lib/async/bus/protocol/transaction.rb +58 -61
- data/lib/async/bus/protocol/wrapper.rb +71 -31
- data/lib/async/bus/server.rb +8 -30
- data/lib/async/bus/version.rb +3 -20
- data/lib/async/bus.rb +3 -19
- data/license.md +21 -0
- data/readme.md +41 -0
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +49 -18
- metadata.gz.sig +3 -0
- data/lib/async/bus/local.rb +0 -61
- data/lib/async/bus/remote.rb +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bcce5759c8ff0eab16a54c2cf8dd48c054dac88bec2705124cc70589563b9fbb
|
|
4
|
+
data.tar.gz: 63b96c8cc50edf9f357df6ea992cb4f6ec63aa89f5349c9e0d8ff85c97dafc91
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a87a86e44c67ca9e4ca76234e0f844c2bb92d14eaffd52f16104f1b14038d266e3725abc584fe779088256c2cdf34e0bfeb4e29aecddeaa71f2d2835f2ed692a
|
|
7
|
+
data.tar.gz: c28eb52cc46f0ae4d3dcbd1c3e823174e273dde9e9dd1678d672b2f0d3ba5704fe495c511c07355be0a1215d0ade1e0824ca36e8f27d20a11f42f00711b65290
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/lib/async/bus/client.rb
CHANGED
|
@@ -1,54 +1,86 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
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.
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
|
22
5
|
|
|
23
|
-
require_relative
|
|
24
|
-
require
|
|
6
|
+
require_relative "protocol/connection"
|
|
7
|
+
require "async/queue"
|
|
25
8
|
|
|
26
9
|
module Async
|
|
27
10
|
module Bus
|
|
28
11
|
class Client
|
|
29
|
-
def initialize(endpoint = nil)
|
|
12
|
+
def initialize(endpoint = nil, **options)
|
|
30
13
|
@endpoint = endpoint || Protocol.local_endpoint
|
|
31
|
-
@
|
|
14
|
+
@options = options
|
|
32
15
|
end
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
17
|
+
# Create a new connection to the server.
|
|
18
|
+
#
|
|
19
|
+
# @returns [Protocol::Connection] The new connection.
|
|
20
|
+
protected def connect!
|
|
21
|
+
peer = @endpoint.connect
|
|
22
|
+
return Protocol::Connection.client(peer, **@options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Called when a connection is established.
|
|
26
|
+
# Override this method to perform setup when a connection is established.
|
|
27
|
+
#
|
|
28
|
+
# @parameter connection [Protocol::Connection] The established connection.
|
|
29
|
+
protected def connected!(connection)
|
|
30
|
+
# Do nothing by default.
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Connect to the server.
|
|
34
|
+
#
|
|
35
|
+
# @parameter persist [Boolean] Whether to keep the connection open indefiniely.
|
|
36
|
+
# @yields {|connection| ...} If a block is given, it will be called with the connection, and the connection will be closed afterwards.
|
|
37
|
+
# @returns [Protocol::Connection] The connection if no block is given.
|
|
38
|
+
def connect(parent: Task.current)
|
|
39
|
+
connection = connect!
|
|
40
|
+
|
|
41
|
+
connection_task = parent.async do
|
|
42
|
+
connection.run
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
connected!(connection)
|
|
46
|
+
|
|
47
|
+
return connection unless block_given?
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
yield(connection, connection_task)
|
|
43
51
|
ensure
|
|
44
52
|
connection_task&.stop
|
|
53
|
+
connection&.close
|
|
45
54
|
end
|
|
46
55
|
end
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
# Run the client in a loop, reconnecting if necessary.
|
|
58
|
+
#
|
|
59
|
+
# Automatically reconnects when the connection fails, with random backoff.
|
|
60
|
+
# This is useful for long-running clients that need to maintain a persistent connection.
|
|
61
|
+
#
|
|
62
|
+
# @parameter parent [Async::Task] The parent task to run under.
|
|
63
|
+
def run(parent: Task.current)
|
|
64
|
+
parent.async(annotation: "Bus Client", transient: true) do |task|
|
|
65
|
+
loop do
|
|
66
|
+
connection = connect!
|
|
67
|
+
|
|
68
|
+
connected_task = task.async do
|
|
69
|
+
connected!(connection)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
connection.run
|
|
73
|
+
rescue => error
|
|
74
|
+
Console.error(self, "Connection failed:", exception: error)
|
|
75
|
+
sleep(rand)
|
|
76
|
+
ensure
|
|
77
|
+
# Ensure any tasks that were created during connection are stopped:
|
|
78
|
+
connected_task&.stop
|
|
79
|
+
|
|
80
|
+
# Close the connection itself:
|
|
81
|
+
connection&.close
|
|
82
|
+
end
|
|
83
|
+
end
|
|
52
84
|
end
|
|
53
85
|
end
|
|
54
86
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Async
|
|
7
|
+
module Bus
|
|
8
|
+
# Base class for controller objects designed to be proxied over Async::Bus.
|
|
9
|
+
#
|
|
10
|
+
# Controllers provide an explicit API for remote operations, avoiding the
|
|
11
|
+
# confusion that comes from proxying generic objects like Array or Hash.
|
|
12
|
+
#
|
|
13
|
+
# @example Array Controller
|
|
14
|
+
# class ArrayController < Async::Bus::Controller
|
|
15
|
+
# def initialize(array)
|
|
16
|
+
# @array = array
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def append(*values)
|
|
20
|
+
# @array.concat(values)
|
|
21
|
+
# self # Return self for chaining
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def get(index)
|
|
25
|
+
# @array[index] # Returns value
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# def size
|
|
29
|
+
# @array.size
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @example Server Setup
|
|
34
|
+
# server.accept do |connection|
|
|
35
|
+
# array = []
|
|
36
|
+
# controller = ArrayController.new(array)
|
|
37
|
+
# connection.bind(:items, controller)
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# @example Client Usage
|
|
41
|
+
# client.connect do |connection|
|
|
42
|
+
# items = connection[:items] # Returns proxy to controller
|
|
43
|
+
# items.append(1, 2, 3) # Remote call
|
|
44
|
+
# expect(items.size).to be == 3
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# Controllers are automatically proxied when serialized if registered
|
|
48
|
+
# as a reference type in the Wrapper:
|
|
49
|
+
#
|
|
50
|
+
# Wrapper.new(connection, reference_types: [Async::Bus::Controller])
|
|
51
|
+
#
|
|
52
|
+
# This allows controller methods to return other controllers and have
|
|
53
|
+
# them automatically proxied.
|
|
54
|
+
class Controller
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
@@ -1,61 +1,77 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
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.
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
|
22
5
|
|
|
23
|
-
require
|
|
24
|
-
require
|
|
6
|
+
require "async"
|
|
7
|
+
require "io/endpoint/unix_endpoint"
|
|
25
8
|
|
|
26
|
-
require_relative
|
|
27
|
-
require_relative
|
|
28
|
-
require_relative
|
|
9
|
+
require_relative "wrapper"
|
|
10
|
+
require_relative "transaction"
|
|
11
|
+
require_relative "proxy"
|
|
12
|
+
require_relative "response"
|
|
29
13
|
|
|
30
14
|
module Async
|
|
31
15
|
module Bus
|
|
32
16
|
module Protocol
|
|
33
17
|
def self.local_endpoint(path = "bus.ipc")
|
|
34
|
-
|
|
18
|
+
::IO::Endpoint.unix(path)
|
|
35
19
|
end
|
|
36
20
|
|
|
37
21
|
class Connection
|
|
38
|
-
def self.client(peer)
|
|
39
|
-
self.new(peer, 1)
|
|
22
|
+
def self.client(peer, **options)
|
|
23
|
+
self.new(peer, 1, **options)
|
|
40
24
|
end
|
|
41
25
|
|
|
42
|
-
def self.server(peer)
|
|
43
|
-
self.new(peer, 2)
|
|
26
|
+
def self.server(peer, **options)
|
|
27
|
+
self.new(peer, 2, **options)
|
|
44
28
|
end
|
|
45
29
|
|
|
46
|
-
def initialize(peer, id)
|
|
30
|
+
def initialize(peer, id, wrapper: Wrapper, timeout: nil)
|
|
47
31
|
@peer = peer
|
|
32
|
+
@id = id
|
|
48
33
|
|
|
49
|
-
@wrapper =
|
|
34
|
+
@wrapper = wrapper.new(self)
|
|
50
35
|
@unpacker = @wrapper.unpacker(peer)
|
|
51
36
|
@packer = @wrapper.packer(peer)
|
|
52
37
|
|
|
38
|
+
@timeout = timeout
|
|
39
|
+
|
|
53
40
|
@transactions = {}
|
|
54
|
-
@id = id
|
|
55
41
|
|
|
56
42
|
@objects = {}
|
|
43
|
+
@proxies = ::ObjectSpace::WeakMap.new
|
|
44
|
+
@finalized = ::Thread::Queue.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @attribute [Float] The timeout for transactions.
|
|
48
|
+
attr_accessor :timeout
|
|
49
|
+
|
|
50
|
+
def flush
|
|
51
|
+
@packer.flush
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def write(message)
|
|
55
|
+
# $stderr.puts "Writing: #{message.inspect}"
|
|
56
|
+
@packer.write(message)
|
|
57
|
+
@packer.flush
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def close
|
|
61
|
+
@transactions.each do |id, transaction|
|
|
62
|
+
transaction.close
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@peer.close
|
|
57
66
|
end
|
|
58
67
|
|
|
68
|
+
def inspect
|
|
69
|
+
"#<#{self.class} #{@objects.size} objects>"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
attr :objects
|
|
73
|
+
attr :proxies
|
|
74
|
+
|
|
59
75
|
attr :unpacker
|
|
60
76
|
attr :packer
|
|
61
77
|
|
|
@@ -68,72 +84,152 @@ module Async
|
|
|
68
84
|
|
|
69
85
|
attr :transactions
|
|
70
86
|
|
|
87
|
+
Explicit = Struct.new(:object) do
|
|
88
|
+
def temporary?
|
|
89
|
+
false
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Implicit = Struct.new(:object) do
|
|
94
|
+
def temporary?
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Bind a local object to a name, such that it could be accessed remotely.
|
|
100
|
+
#
|
|
101
|
+
# @returns [Proxy] A proxy instance for the bound object.
|
|
102
|
+
def bind(name, object)
|
|
103
|
+
# Bind the object into the local object store (explicitly bound, not temporary):
|
|
104
|
+
@objects[name] = Explicit.new(object)
|
|
105
|
+
|
|
106
|
+
# Return the proxy instance for the bound object:
|
|
107
|
+
return self[name]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Generate a proxy name for an object and bind it.
|
|
111
|
+
#
|
|
112
|
+
# @returns [Proxy] A proxy instance for the bound object.
|
|
71
113
|
def proxy(object)
|
|
72
|
-
name = "
|
|
114
|
+
name = "<#{object.class}@#{next_id.to_s(16)}>".freeze
|
|
115
|
+
|
|
116
|
+
# Bind the object into the local object store (temporary):
|
|
117
|
+
@objects[name] = Implicit.new(object)
|
|
118
|
+
|
|
119
|
+
# This constructs the Proxy instance:
|
|
120
|
+
return self[name]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Generate a proxy name for an object and bind it, returning just the name.
|
|
124
|
+
# Used for serialization when you need the name string, not a Proxy instance.
|
|
125
|
+
#
|
|
126
|
+
# @returns [String] The name of the bound object.
|
|
127
|
+
def proxy_name(object)
|
|
128
|
+
name = "<#{object.class}@#{next_id.to_s(16)}>".freeze
|
|
73
129
|
|
|
74
|
-
|
|
130
|
+
# Bind the object into the local object store (temporary):
|
|
131
|
+
@objects[name] = Implicit.new(object)
|
|
75
132
|
|
|
133
|
+
# Return the name:
|
|
76
134
|
return name
|
|
77
135
|
end
|
|
78
136
|
|
|
79
|
-
def
|
|
80
|
-
@objects[name]
|
|
137
|
+
def object(name)
|
|
138
|
+
@objects[name]&.object
|
|
81
139
|
end
|
|
82
140
|
|
|
83
|
-
def
|
|
84
|
-
|
|
141
|
+
private def finalize(name)
|
|
142
|
+
proc do
|
|
143
|
+
@finalized.push(name) rescue nil
|
|
144
|
+
end
|
|
85
145
|
end
|
|
86
146
|
|
|
87
|
-
def
|
|
88
|
-
|
|
147
|
+
def []=(name, object)
|
|
148
|
+
@objects[name] = Explicit.new(object)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def [](name)
|
|
152
|
+
unless proxy = @proxies[name]
|
|
153
|
+
proxy = Proxy.new(self, name)
|
|
154
|
+
@proxies[name] = proxy
|
|
155
|
+
|
|
156
|
+
::ObjectSpace.define_finalizer(proxy, finalize(name))
|
|
157
|
+
end
|
|
89
158
|
|
|
90
|
-
|
|
159
|
+
return proxy
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def transaction!(id = self.next_id)
|
|
163
|
+
transaction = Transaction.new(self, id, timeout: @timeout)
|
|
91
164
|
@transactions[id] = transaction
|
|
92
165
|
|
|
166
|
+
return transaction
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def invoke(name, arguments, options = {}, &block)
|
|
170
|
+
transaction = self.transaction!
|
|
171
|
+
|
|
93
172
|
transaction.invoke(name, arguments, options, &block)
|
|
173
|
+
ensure
|
|
174
|
+
transaction&.close
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def send_release(name)
|
|
178
|
+
self.write(Release.new(name))
|
|
94
179
|
end
|
|
95
180
|
|
|
96
|
-
def run
|
|
97
|
-
|
|
181
|
+
def run(parent: Task.current)
|
|
182
|
+
finalizer_task = parent.async do
|
|
183
|
+
while name = @finalized.pop
|
|
184
|
+
self.send_release(name)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
98
188
|
@unpacker.each do |message|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
189
|
+
case message
|
|
190
|
+
when Invoke
|
|
191
|
+
# If the object is not found, send an error response and skip the transaction:
|
|
192
|
+
if object = @objects[message.name]&.object
|
|
193
|
+
transaction = self.transaction!(message.id)
|
|
194
|
+
|
|
195
|
+
parent.async(annotation: "Invoke #{message.name}") do
|
|
196
|
+
# $stderr.puts "-> Accepting: #{message.name} #{message.arguments.inspect} #{message.options.inspect}"
|
|
197
|
+
transaction.accept(object, message.arguments, message.options, message.block_given)
|
|
198
|
+
ensure
|
|
199
|
+
# $stderr.puts "<- Accepted: #{message.name}"
|
|
200
|
+
# This will also delete the transaction from @transactions:
|
|
201
|
+
transaction.close
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
self.write(Error.new(message.id, NameError.new("Object not found: #{message.name}")))
|
|
205
|
+
end
|
|
206
|
+
when Response
|
|
207
|
+
if transaction = @transactions[message.id]
|
|
208
|
+
transaction.push(message)
|
|
209
|
+
else
|
|
210
|
+
# Stale message - transaction already closed (e.g. timeout) or never existed (ignore silently).
|
|
211
|
+
end
|
|
212
|
+
when Release
|
|
213
|
+
name = message.name
|
|
214
|
+
if @objects[name]&.temporary?
|
|
215
|
+
# Only delete temporary objects, not explicitly bound ones:
|
|
216
|
+
@objects.delete(name)
|
|
116
217
|
end
|
|
117
218
|
else
|
|
118
|
-
|
|
219
|
+
Console.error(self, "Unexpected message:", message)
|
|
119
220
|
end
|
|
120
221
|
end
|
|
121
222
|
ensure
|
|
122
|
-
|
|
123
|
-
transaction.close
|
|
124
|
-
end
|
|
223
|
+
finalizer_task&.stop
|
|
125
224
|
|
|
126
|
-
@transactions.clear
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def close
|
|
130
225
|
@transactions.each do |id, transaction|
|
|
131
226
|
transaction.close
|
|
132
227
|
end
|
|
133
228
|
|
|
134
|
-
@
|
|
229
|
+
@transactions.clear
|
|
230
|
+
@proxies = ::ObjectSpace::WeakMap.new
|
|
135
231
|
end
|
|
136
232
|
end
|
|
137
233
|
end
|
|
138
234
|
end
|
|
139
|
-
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "msgpack"
|
|
7
|
+
require_relative "proxy"
|
|
8
|
+
|
|
9
|
+
module Async
|
|
10
|
+
module Bus
|
|
11
|
+
module Protocol
|
|
12
|
+
# Represents a method invocation.
|
|
13
|
+
class Invoke
|
|
14
|
+
def initialize(id, name, arguments, options, block_given)
|
|
15
|
+
@id = id
|
|
16
|
+
@name = name
|
|
17
|
+
@arguments = arguments
|
|
18
|
+
@options = options
|
|
19
|
+
@block_given = block_given
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr :id
|
|
23
|
+
attr :name
|
|
24
|
+
attr :arguments
|
|
25
|
+
attr :options
|
|
26
|
+
attr :block_given
|
|
27
|
+
|
|
28
|
+
def pack(packer)
|
|
29
|
+
packer.write(@id)
|
|
30
|
+
packer.write(@name)
|
|
31
|
+
|
|
32
|
+
packer.write(@arguments.size)
|
|
33
|
+
@arguments.each do |argument|
|
|
34
|
+
packer.write(argument)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
packer.write(@options.size)
|
|
38
|
+
@options.each do |key, value|
|
|
39
|
+
packer.write(key)
|
|
40
|
+
packer.write(value)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
packer.write(@block_given)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.unpack(unpacker)
|
|
47
|
+
id = unpacker.read
|
|
48
|
+
name = unpacker.read
|
|
49
|
+
arguments = Array.new(unpacker.read){unpacker.read}
|
|
50
|
+
options = Array.new(unpacker.read){[unpacker.read, unpacker.read]}.to_h
|
|
51
|
+
block_given = unpacker.read
|
|
52
|
+
|
|
53
|
+
return self.new(id, name, arguments, options, block_given)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -1,42 +1,54 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
-
# furnished to do so, subject to the following conditions:
|
|
9
|
-
#
|
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
|
11
|
-
# all copies or substantial portions of the Software.
|
|
12
|
-
#
|
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
-
# THE SOFTWARE.
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
|
20
5
|
|
|
21
6
|
module Async
|
|
22
7
|
module Bus
|
|
23
8
|
module Protocol
|
|
9
|
+
# A proxy object that forwards method calls to a remote object.
|
|
10
|
+
#
|
|
11
|
+
# We must be extremely careful not to invoke any methods on the proxy object that would recursively call the proxy object.
|
|
24
12
|
class Proxy < BasicObject
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
# Create a new proxy object.
|
|
14
|
+
#
|
|
15
|
+
# @parameter connection [Connection] The connection to the remote object.
|
|
16
|
+
# @parameter name [Symbol] The name (address) of the remote object.
|
|
17
|
+
def initialize(connection, name)
|
|
18
|
+
@connection = connection
|
|
27
19
|
@name = name
|
|
28
20
|
end
|
|
29
21
|
|
|
30
|
-
def
|
|
31
|
-
|
|
22
|
+
def __name__
|
|
23
|
+
@name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def !
|
|
27
|
+
@connection.invoke(@name, [:!])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def == object
|
|
31
|
+
@connection.invoke(@name, [:==, object])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def != object
|
|
35
|
+
@connection.invoke(@name, [:!=, object])
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def method_missing(*arguments, **options, &block)
|
|
35
|
-
@
|
|
39
|
+
@connection.invoke(@name, arguments, options, &block)
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def respond_to?(name, include_all = false)
|
|
39
|
-
@
|
|
43
|
+
@connection.invoke(@name, [:respond_to?, name, include_all])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def respond_to_missing?(name, include_all = false)
|
|
47
|
+
@connection.invoke(@name, [:respond_to?, name, include_all])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def inspect
|
|
51
|
+
"#<proxy #{@name}: #{@connection.invoke(@name, [:inspect])}>"
|
|
40
52
|
end
|
|
41
53
|
end
|
|
42
54
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Async
|
|
7
|
+
module Bus
|
|
8
|
+
module Protocol
|
|
9
|
+
# Represents a named object that has been released (no longer available).
|
|
10
|
+
class Release
|
|
11
|
+
def initialize(name)
|
|
12
|
+
@name = name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr :name
|
|
16
|
+
|
|
17
|
+
def pack(packer)
|
|
18
|
+
packer.write(@name)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.unpack(unpacker)
|
|
22
|
+
name = unpacker.read
|
|
23
|
+
|
|
24
|
+
return self.new(name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|