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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0e7757f47bb17be90f64d3bcdd28ba17e5e8099a505062a7e7c3dd71aaedf37
4
- data.tar.gz: cbf55606a5a16e12cb4ef47b73756a1f3ef3b65c1e500583928a44a4d28b97c4
3
+ metadata.gz: dcd3d0dc3ef5c1d53209f273d6b5781b335c1dd11e9d3f41ddd1b709c7a61904
4
+ data.tar.gz: 31374c47b19b8c121904ffa042c13910e0512879a63dde9392f69ce0d6a81004
5
5
  SHA512:
6
- metadata.gz: 1e5e470938bc069179be121de52a5e2986479a7fa8dec030d743de246fef73812729bc57ea67d4958c076e42e8d23ef703c0931e51544f206352e3ba45a3c463
7
- data.tar.gz: bc3d7083a9617ad49b2b139d0948bec69257ad5991be08525de8228e87aa3457bdd9d4d2745b399bf20f0266143428565e3fb5d88e039255687eb90b38c7efe9
6
+ metadata.gz: '091c51ec3e5869a0acea502c8487e7bbecf45bf4f45549e26e06ee5eb95462a216e03e067dfb4081675e1ea94a662c4ee39a27a4d7adb80e7f8e6347ca45ac4f'
7
+ data.tar.gz: 4f5f4d68d1573229567c85b8835348e6e165e96f904583ae3e3b054f950dcaf324b9b6d8b912ecc49f13332af6f9009acddf8a670460e714dbdd172150825e03
@@ -61,6 +61,7 @@ runtime_cpp_files = %w[
61
61
  runtime_events.cpp
62
62
  runtime_signals.cpp
63
63
  runtime_value_types.cpp
64
+ runtime_virtual_methods.cpp
64
65
  ].map { |name| File.expand_path("../../ext/qt_ruby_bridge/#{name}", __dir__) }
65
66
 
66
67
  unless File.exist?(generated_cpp)
@@ -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
+ }
@@ -23,7 +23,7 @@ module Qt
23
23
 
24
24
  def exec
25
25
  @windows.each(&:show)
26
- Native.qapplication_exec(@handle)
26
+ Qt::VirtualMethodRuntime.after_native_call(Native.qapplication_exec(@handle))
27
27
  ensure
28
28
  dispose
29
29
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qt
4
+ module GeneratedOverloadRuntime
5
+ OMITTED = Object.new.freeze
6
+ end
7
+ end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qt
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -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('__effective_access' => access)
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(:methods_by_class, :bases_by_class, :ctors_by_class, :ctor_decls_by_class, :abstract_by_class)
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)