qt 0.2.0 → 0.2.1
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
- data/ext/qt_ruby_bridge/extconf.rb +1 -0
- data/ext/qt_ruby_bridge/qt_ruby_runtime.hpp +8 -0
- data/ext/qt_ruby_bridge/runtime_virtual_methods.cpp +78 -0
- data/lib/qt/application_lifecycle.rb +1 -1
- data/lib/qt/generated_overload_runtime.rb +7 -0
- data/lib/qt/object_wrapper.rb +1 -0
- data/lib/qt/version.rb +1 -1
- data/lib/qt/virtual_method_runtime.rb +165 -0
- data/lib/qt.rb +3 -0
- data/scripts/generate_bridge/ast_introspection.rb +16 -3
- data/scripts/generate_bridge/auto_methods.rb +171 -20
- data/scripts/generate_bridge/cpp_method_return_emitter.rb +15 -0
- data/scripts/generate_bridge/ffi_api.rb +1 -1
- data/scripts/generate_bridge/free_function_specs.rb +0 -158
- data/scripts/generate_bridge/spec_discovery.rb +51 -12
- data/scripts/generate_bridge.rb +374 -30
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dcd3d0dc3ef5c1d53209f273d6b5781b335c1dd11e9d3f41ddd1b709c7a61904
|
|
4
|
+
data.tar.gz: 31374c47b19b8c121904ffa042c13910e0512879a63dde9392f69ce0d6a81004
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '091c51ec3e5869a0acea502c8487e7bbecf45bf4f45549e26e06ee5eb95462a216e03e067dfb4081675e1ea94a662c4ee39a27a4d7adb80e7f8e6347ca45ac4f'
|
|
7
|
+
data.tar.gz: 4f5f4d68d1573229567c85b8835348e6e165e96f904583ae3e3b054f950dcaf324b9b6d8b912ecc49f13332af6f9009acddf8a670460e714dbdd172150825e03
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <QApplication>
|
|
4
|
+
#include <QByteArray>
|
|
4
5
|
#include <QList>
|
|
5
6
|
#include <QVariant>
|
|
6
7
|
|
|
7
8
|
namespace QtRubyRuntime {
|
|
8
9
|
using EventCallback = int (*)(void*, int, const char*);
|
|
9
10
|
using SignalCallback = void (*)(void*, int, const char*);
|
|
11
|
+
using VirtualMethodCallback = void* (*)(void*, int, const char*);
|
|
10
12
|
|
|
11
13
|
// Creates/owns the singleton QApplication used by the Ruby runtime bridge.
|
|
12
14
|
// The implementation records the creating thread as GUI thread for shutdown checks.
|
|
@@ -26,4 +28,10 @@ void unwatch_qobject_event(void* object_handle, int event_type);
|
|
|
26
28
|
void set_signal_callback(void* callback_ptr);
|
|
27
29
|
int qobject_connect_signal(void* object_handle, const char* signal_name);
|
|
28
30
|
int qobject_disconnect_signal(void* object_handle, const char* signal_name);
|
|
31
|
+
|
|
32
|
+
void set_virtual_method_callback(void* callback_ptr);
|
|
33
|
+
void set_virtual_dispatch_enabled(void* object_handle, int enabled);
|
|
34
|
+
int virtual_dispatch_enabled(void* object_handle);
|
|
35
|
+
QByteArray dispatch_virtual_method(const void* object_handle, int method_id, const QByteArray& arguments);
|
|
36
|
+
bool ruby_virtual_method_active(const void* object_handle, int method_id);
|
|
29
37
|
} // namespace QtRubyRuntime
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#include "qt_ruby_runtime.hpp"
|
|
2
|
+
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <unordered_set>
|
|
5
|
+
#include <mutex>
|
|
6
|
+
|
|
7
|
+
namespace QtRubyRuntime {
|
|
8
|
+
namespace {
|
|
9
|
+
struct ActiveVirtualMethod {
|
|
10
|
+
const void* object_handle;
|
|
11
|
+
int method_id;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
VirtualMethodCallback& virtual_method_callback_ref() {
|
|
15
|
+
static VirtualMethodCallback callback = nullptr;
|
|
16
|
+
return callback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
std::vector<ActiveVirtualMethod>& active_virtual_methods() {
|
|
20
|
+
thread_local std::vector<ActiveVirtualMethod> active;
|
|
21
|
+
return active;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
std::unordered_set<const void*>& virtual_dispatch_objects() {
|
|
25
|
+
static std::unordered_set<const void*> objects;
|
|
26
|
+
return objects;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
std::mutex& virtual_dispatch_mutex() {
|
|
30
|
+
static std::mutex mutex;
|
|
31
|
+
return mutex;
|
|
32
|
+
}
|
|
33
|
+
} // namespace
|
|
34
|
+
} // namespace QtRubyRuntime
|
|
35
|
+
|
|
36
|
+
void QtRubyRuntime::set_virtual_method_callback(void* callback_ptr) {
|
|
37
|
+
virtual_method_callback_ref() = reinterpret_cast<VirtualMethodCallback>(callback_ptr);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void QtRubyRuntime::set_virtual_dispatch_enabled(void* object_handle, int enabled) {
|
|
41
|
+
if (!object_handle) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
std::lock_guard<std::mutex> lock(virtual_dispatch_mutex());
|
|
45
|
+
if (enabled != 0) {
|
|
46
|
+
virtual_dispatch_objects().insert(object_handle);
|
|
47
|
+
} else {
|
|
48
|
+
virtual_dispatch_objects().erase(object_handle);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
int QtRubyRuntime::virtual_dispatch_enabled(void* object_handle) {
|
|
53
|
+
std::lock_guard<std::mutex> lock(virtual_dispatch_mutex());
|
|
54
|
+
return virtual_dispatch_objects().find(object_handle) != virtual_dispatch_objects().end();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
QByteArray QtRubyRuntime::dispatch_virtual_method(const void* object_handle, int method_id, const QByteArray& arguments) {
|
|
58
|
+
VirtualMethodCallback callback = virtual_method_callback_ref();
|
|
59
|
+
if (!callback) {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
auto& active = active_virtual_methods();
|
|
64
|
+
active.push_back({object_handle, method_id});
|
|
65
|
+
void* response = callback(const_cast<void*>(object_handle), method_id, arguments.constData());
|
|
66
|
+
active.pop_back();
|
|
67
|
+
return response ? QByteArray(static_cast<const char*>(response)) : QByteArray();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
bool QtRubyRuntime::ruby_virtual_method_active(const void* object_handle, int method_id) {
|
|
71
|
+
const auto& active = active_virtual_methods();
|
|
72
|
+
for (auto it = active.rbegin(); it != active.rend(); ++it) {
|
|
73
|
+
if (it->object_handle == object_handle && it->method_id == method_id) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
data/lib/qt/object_wrapper.rb
CHANGED
|
@@ -172,6 +172,7 @@ module Qt
|
|
|
172
172
|
destroy_hook_addresses.delete(address)
|
|
173
173
|
Qt::EventRuntime.clear_signal_registrations_for_address(address)
|
|
174
174
|
invalidate_cached_wrapper(address, wrapper)
|
|
175
|
+
wrapper.instance_variable_set(:@handle, nil) if wrapper.handle&.address == address
|
|
175
176
|
end
|
|
176
177
|
rescue StandardError
|
|
177
178
|
destroy_hook_addresses.delete(address)
|
data/lib/qt/version.rb
CHANGED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'weakref'
|
|
5
|
+
|
|
6
|
+
module Qt
|
|
7
|
+
# Dispatches generated C++ virtual overrides to methods defined by Ruby subclasses.
|
|
8
|
+
module VirtualMethodRuntime
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def install!(descriptors)
|
|
12
|
+
@descriptors = descriptors.to_h { |descriptor| [descriptor.fetch(:id), symbolize_keys(descriptor)] }
|
|
13
|
+
return if @callback
|
|
14
|
+
|
|
15
|
+
@callback = FFI::Function.new(:pointer, %i[pointer int string]) do |object_handle, method_id, payload|
|
|
16
|
+
dispatch(object_handle, method_id, payload)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ensure_installed!
|
|
21
|
+
return if @native_installed
|
|
22
|
+
|
|
23
|
+
Native.define_bridge_wrappers!
|
|
24
|
+
Native.set_virtual_method_callback(@callback)
|
|
25
|
+
@native_installed = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def attach(wrapper)
|
|
29
|
+
ensure_installed!
|
|
30
|
+
enabled = @descriptors&.values&.any? { |descriptor| overridden_method_name(wrapper, descriptor) }
|
|
31
|
+
handle = wrapper.handle
|
|
32
|
+
Native.set_virtual_dispatch_enabled(handle, enabled ? 1 : 0)
|
|
33
|
+
if enabled
|
|
34
|
+
wrapper_cache[handle.address] = WeakRef.new(wrapper)
|
|
35
|
+
else
|
|
36
|
+
wrapper_cache.delete(handle.address)
|
|
37
|
+
end
|
|
38
|
+
wrapper
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dispatch(object_handle, method_id, payload)
|
|
42
|
+
descriptor = @descriptors&.[](method_id)
|
|
43
|
+
return callback_response(handled: false) unless descriptor
|
|
44
|
+
|
|
45
|
+
object_address = pointer_address(object_handle)
|
|
46
|
+
return callback_response(handled: false) if object_address.zero?
|
|
47
|
+
|
|
48
|
+
object_pointer = FFI::Pointer.new(object_address)
|
|
49
|
+
wrapper = ObjectWrapper.cached_wrapper_for(object_pointer) || cached_wrapper_for(object_address)
|
|
50
|
+
return callback_response(handled: false) unless wrapper
|
|
51
|
+
|
|
52
|
+
ruby_method = overridden_method_name(wrapper, descriptor)
|
|
53
|
+
return callback_response(handled: false) unless ruby_method
|
|
54
|
+
|
|
55
|
+
encoded_args = payload.nil? || payload.empty? ? [] : JSON.parse(StringCodec.from_qt_text(payload))
|
|
56
|
+
args = descriptor.fetch(:args).zip(encoded_args).map do |arg_descriptor, value|
|
|
57
|
+
decode_argument(arg_descriptor, value)
|
|
58
|
+
end
|
|
59
|
+
value = wrapper.__send__(ruby_method, *args)
|
|
60
|
+
callback_response(handled: true, value: encode_return(descriptor.fetch(:return), value))
|
|
61
|
+
rescue Exception => error # FFI callbacks must not unwind a Ruby exception through C++.
|
|
62
|
+
(@callback_errors ||= {})[Thread.current.object_id] = error
|
|
63
|
+
callback_response(handled: false)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def raise_pending_error!
|
|
67
|
+
error = @callback_errors&.delete(Thread.current.object_id)
|
|
68
|
+
raise error if error
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def after_native_call(result)
|
|
72
|
+
raise_pending_error!
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def decode_argument(descriptor, value)
|
|
77
|
+
case descriptor.fetch(:kind)
|
|
78
|
+
when :integer then Integer(value)
|
|
79
|
+
when :boolean then value == true
|
|
80
|
+
when :string then value
|
|
81
|
+
when :pointer, :value then wrap_borrowed_pointer(value, descriptor[:qt_class])
|
|
82
|
+
else raise ArgumentError, "unsupported virtual argument kind: #{descriptor[:kind].inspect}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def wrap_borrowed_pointer(value, qt_class)
|
|
87
|
+
address = value.nil? ? 0 : Integer(value)
|
|
88
|
+
return nil if address.zero?
|
|
89
|
+
|
|
90
|
+
pointer = FFI::Pointer.new(address)
|
|
91
|
+
klass = Qt.const_get(qt_class, false)
|
|
92
|
+
return ObjectWrapper.wrap(pointer, qt_class) if klass <= QObject
|
|
93
|
+
|
|
94
|
+
wrapper = klass.allocate
|
|
95
|
+
wrapper.instance_variable_set(:@handle, pointer)
|
|
96
|
+
wrapper
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def encode_return(descriptor, value)
|
|
100
|
+
case descriptor.fetch(:kind)
|
|
101
|
+
when :void then nil
|
|
102
|
+
when :integer
|
|
103
|
+
Integer(value)
|
|
104
|
+
when :boolean
|
|
105
|
+
value == true
|
|
106
|
+
when :string
|
|
107
|
+
value.nil? ? '' : String(value)
|
|
108
|
+
when :pointer, :value
|
|
109
|
+
return nil if value.nil?
|
|
110
|
+
raise TypeError, 'Qt pointer return must be a wrapper with a handle' unless value.respond_to?(:handle)
|
|
111
|
+
|
|
112
|
+
value.handle.address.to_s
|
|
113
|
+
else raise ArgumentError, "unsupported virtual return kind: #{descriptor[:kind].inspect}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def callback_response(handled:, value: nil)
|
|
118
|
+
response = JSON.generate(handled: handled, value: value)
|
|
119
|
+
buffer = FFI::MemoryPointer.from_string(response)
|
|
120
|
+
(@callback_buffers ||= {})[Thread.current.object_id] = buffer
|
|
121
|
+
buffer
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def overridden_method_name(wrapper, descriptor)
|
|
125
|
+
wrapper_class = Qt.const_get(descriptor.fetch(:wrapper_class), false)
|
|
126
|
+
return nil unless wrapper.class.ancestors.include?(wrapper_class)
|
|
127
|
+
|
|
128
|
+
candidates = [descriptor.fetch(:ruby_name).to_sym, descriptor.fetch(:snake_name).to_sym].uniq
|
|
129
|
+
wrapper.class.ancestors.take_while { |ancestor| ancestor != wrapper_class }.each do |ancestor|
|
|
130
|
+
candidates.each do |candidate|
|
|
131
|
+
methods = ancestor.instance_methods(false) + ancestor.private_instance_methods(false)
|
|
132
|
+
return candidate if methods.include?(candidate)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def wrapper_cache
|
|
139
|
+
@wrapper_cache ||= {}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def cached_wrapper_for(address)
|
|
143
|
+
reference = wrapper_cache[address]
|
|
144
|
+
reference&.__getobj__
|
|
145
|
+
rescue WeakRef::RefError
|
|
146
|
+
wrapper_cache.delete(address)
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def pointer_address(pointer)
|
|
151
|
+
return 0 if pointer.nil?
|
|
152
|
+
return pointer.address if pointer.respond_to?(:address)
|
|
153
|
+
|
|
154
|
+
Integer(pointer)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def symbolize_keys(value)
|
|
158
|
+
case value
|
|
159
|
+
when Hash then value.to_h { |key, nested| [key.to_sym, symbolize_keys(nested)] }
|
|
160
|
+
when Array then value.map { |nested| symbolize_keys(nested) }
|
|
161
|
+
else value
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
data/lib/qt.rb
CHANGED
|
@@ -32,11 +32,14 @@ require_relative 'qt/bridge'
|
|
|
32
32
|
require_relative 'qt/native'
|
|
33
33
|
require_relative 'qt/generated_setter_aliases_runtime'
|
|
34
34
|
require_relative 'qt/generated_singleton_forwarders_runtime'
|
|
35
|
+
require_relative 'qt/generated_overload_runtime'
|
|
36
|
+
require_relative 'qt/virtual_method_runtime'
|
|
35
37
|
require_relative 'qt/event_runtime_dispatch'
|
|
36
38
|
require_relative 'qt/event_runtime_qobject_methods'
|
|
37
39
|
require_relative 'qt/event_runtime'
|
|
38
40
|
require GENERATED_EVENT_PAYLOADS
|
|
39
41
|
require GENERATED_WIDGETS
|
|
42
|
+
Qt::VirtualMethodRuntime.install!(Qt::GENERATED_VIRTUAL_METHODS)
|
|
40
43
|
Qt::GeneratedSetterAliasesRuntime.apply!(Qt)
|
|
41
44
|
Qt::GeneratedSingletonForwardersRuntime.apply!(Qt)
|
|
42
45
|
Qt::ObjectWrapper.install_constructor_cache_hooks!
|
|
@@ -55,7 +55,10 @@ class AstClassMemberRecorder
|
|
|
55
55
|
return false unless inner['kind'] == 'CXXMethodDecl' && inner['name']
|
|
56
56
|
|
|
57
57
|
access = inner['access'] || current_access
|
|
58
|
-
@methods_by_class[@class_name][inner['name']] << inner.merge(
|
|
58
|
+
@methods_by_class[@class_name][inner['name']] << inner.merge(
|
|
59
|
+
'__effective_access' => access,
|
|
60
|
+
'__owner_class' => @class_name
|
|
61
|
+
)
|
|
59
62
|
true
|
|
60
63
|
end
|
|
61
64
|
|
|
@@ -172,7 +175,8 @@ def ast_class_index_constructor_collections
|
|
|
172
175
|
{
|
|
173
176
|
ctors_by_class: Hash.new { |h, k| h[k] = [] },
|
|
174
177
|
ctor_decls_by_class: Hash.new { |h, k| h[k] = [] },
|
|
175
|
-
abstract_by_class: Hash.new(false)
|
|
178
|
+
abstract_by_class: Hash.new(false),
|
|
179
|
+
final_by_class: Hash.new(false)
|
|
176
180
|
}
|
|
177
181
|
end
|
|
178
182
|
|
|
@@ -189,6 +193,8 @@ def ast_index_track_record_decl(node, data)
|
|
|
189
193
|
return if class_name.nil? || class_name.empty?
|
|
190
194
|
|
|
191
195
|
data[:abstract_by_class][class_name] ||= node.dig('definitionData', 'isAbstract') == true
|
|
196
|
+
data[:final_by_class][class_name] ||= node['final'] == true ||
|
|
197
|
+
Array(node['inner']).any? { |child| child['kind'] == 'FinalAttr' }
|
|
192
198
|
ast_record_base_classes(node, class_name, data[:bases_by_class])
|
|
193
199
|
method_count, ctor_count = ast_record_class_members(
|
|
194
200
|
node, class_name, data[:methods_by_class], data[:ctors_by_class], data[:ctor_decls_by_class]
|
|
@@ -202,7 +208,14 @@ def finalize_ast_class_index!(data)
|
|
|
202
208
|
data[:ctors_by_class].each_value(&:uniq!)
|
|
203
209
|
debug_log("ast_class_index classes=#{data[:methods_by_class].length} method_decls=#{data[:method_decl_count]}")
|
|
204
210
|
debug_log("ast_class_index ctor_decls=#{data[:ctor_decl_count]}")
|
|
205
|
-
data.slice(
|
|
211
|
+
data.slice(
|
|
212
|
+
:methods_by_class,
|
|
213
|
+
:bases_by_class,
|
|
214
|
+
:ctors_by_class,
|
|
215
|
+
:ctor_decls_by_class,
|
|
216
|
+
:abstract_by_class,
|
|
217
|
+
:final_by_class
|
|
218
|
+
)
|
|
206
219
|
end
|
|
207
220
|
|
|
208
221
|
def ast_class_index(ast)
|