jolt-ruby 1.0.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 +7 -0
- data/.gitmodules +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +228 -0
- data/Rakefile +180 -0
- data/examples/character_virtual.rb +35 -0
- data/examples/falling_sphere.rb +25 -0
- data/examples/stagecraft_binding.rb +29 -0
- data/ext/jolt_ruby/CMakeLists.txt +42 -0
- data/ext/jolt_ruby/character_helper.cpp +44 -0
- data/ext/jolt_ruby/constraint_helper.cpp +154 -0
- data/ext/jolt_ruby/extconf.rb +56 -0
- data/ext/jolt_ruby/helper.cpp +225 -0
- data/ext/jolt_ruby/helper.h +103 -0
- data/ext/joltc/.github/FUNDING.yml +1 -0
- data/ext/joltc/.github/workflows/build.yml +298 -0
- data/ext/joltc/.gitignore +272 -0
- data/ext/joltc/CMakeLists.txt +431 -0
- data/ext/joltc/LICENSE +21 -0
- data/ext/joltc/README.md +10 -0
- data/ext/joltc/build/cmake_vs2026_arm64.bat +3 -0
- data/ext/joltc/build/cmake_vs2026_clang.bat +10 -0
- data/ext/joltc/build/cmake_vs2026_x64.bat +3 -0
- data/ext/joltc/build/cmake_vs2026_x64_double.bat +3 -0
- data/ext/joltc/include/joltc.h +3166 -0
- data/ext/joltc/samples/01_HelloWorld/main.cpp +170 -0
- data/ext/joltc/samples/CMakeLists.txt +35 -0
- data/ext/joltc/src/joltc.c +4 -0
- data/ext/joltc/src/joltc.cpp +11812 -0
- data/ext/joltc/src/joltc_assert.cpp +271 -0
- data/ext/joltc/tests/CMakeLists.txt +77 -0
- data/ext/joltc/tests/test_character.cpp +149 -0
- data/ext/joltc/tests/test_collision.cpp +146 -0
- data/ext/joltc/tests/test_constraints.cpp +353 -0
- data/ext/joltc/tests/test_core.cpp +94 -0
- data/ext/joltc/tests/test_math.cpp +585 -0
- data/ext/joltc/tests/test_physics_system.cpp +465 -0
- data/ext/joltc/tests/test_shapes.cpp +789 -0
- data/ext/joltc/tests/test_skeleton.cpp +370 -0
- data/ext/joltc/tests/test_vehicle.cpp +319 -0
- data/generator/generate.rb +237 -0
- data/generator/layout_probe.cpp +489 -0
- data/generator/verify_layout.rb +32 -0
- data/lib/jolt/body.rb +147 -0
- data/lib/jolt/body_collection.rb +115 -0
- data/lib/jolt/body_dynamics.rb +93 -0
- data/lib/jolt/character_virtual.rb +162 -0
- data/lib/jolt/constraint.rb +136 -0
- data/lib/jolt/constraint_collection.rb +123 -0
- data/lib/jolt/contact_events.rb +19 -0
- data/lib/jolt/conversions.rb +76 -0
- data/lib/jolt/errors.rb +12 -0
- data/lib/jolt/fixed_stepper.rb +37 -0
- data/lib/jolt/hit.rb +5 -0
- data/lib/jolt/layers.rb +182 -0
- data/lib/jolt/native/character_functions.rb +16 -0
- data/lib/jolt/native/constraint_functions.rb +27 -0
- data/lib/jolt/native/core_functions.rb +17 -0
- data/lib/jolt/native/generated.rb +1995 -0
- data/lib/jolt/native/platform.rb +51 -0
- data/lib/jolt/native/query_functions.rb +43 -0
- data/lib/jolt/native/types.rb +28 -0
- data/lib/jolt/native.rb +94 -0
- data/lib/jolt/shape.rb +90 -0
- data/lib/jolt/shape_builders.rb +155 -0
- data/lib/jolt/system.rb +238 -0
- data/lib/jolt/system_characters.rb +25 -0
- data/lib/jolt/system_constraints.rb +36 -0
- data/lib/jolt/system_contacts.rb +57 -0
- data/lib/jolt/system_queries.rb +111 -0
- data/lib/jolt/transform.rb +5 -0
- data/lib/jolt/version.rb +5 -0
- data/lib/jolt.rb +83 -0
- data/script/smoke_gem.rb +29 -0
- data/script/verify_release.rb +36 -0
- metadata +147 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi/clang"
|
|
4
|
+
|
|
5
|
+
class BindingGenerator
|
|
6
|
+
ROOT = File.expand_path("..", __dir__)
|
|
7
|
+
HEADER = File.join(ROOT, "ext", "joltc", "include", "joltc.h")
|
|
8
|
+
RUBY_OUTPUT = File.join(ROOT, "lib", "jolt", "native", "generated.rb")
|
|
9
|
+
PROBE_OUTPUT = File.join(__dir__, "layout_probe.cpp")
|
|
10
|
+
|
|
11
|
+
INTEGER_TYPES = {
|
|
12
|
+
type_char_s: :int8,
|
|
13
|
+
type_schar: :int8,
|
|
14
|
+
type_uchar: :uint8,
|
|
15
|
+
type_short: :int16,
|
|
16
|
+
type_ushort: :uint16,
|
|
17
|
+
type_int: :int32,
|
|
18
|
+
type_uint: :uint32,
|
|
19
|
+
type_long: :long,
|
|
20
|
+
type_ulong: :ulong,
|
|
21
|
+
type_longlong: :int64,
|
|
22
|
+
type_ulonglong: :uint64
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@translation_unit = FFI::Clang::Index.new.parse_translation_unit(
|
|
27
|
+
HEADER, %w[-x c++ -std=c++17]
|
|
28
|
+
)
|
|
29
|
+
errors = @translation_unit.diagnostics.select { |diagnostic| diagnostic.severity >= 3 }
|
|
30
|
+
abort errors.map(&:spelling).join("\n") unless errors.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def run(check: false)
|
|
34
|
+
outputs = {
|
|
35
|
+
RUBY_OUTPUT => render_ruby,
|
|
36
|
+
PROBE_OUTPUT => render_probe
|
|
37
|
+
}
|
|
38
|
+
if check
|
|
39
|
+
stale = outputs.filter_map { |path, content| path unless File.binread(path) == content }
|
|
40
|
+
abort "generated bindings are stale: #{stale.join(", ")}" unless stale.empty?
|
|
41
|
+
else
|
|
42
|
+
outputs.each { |path, content| File.binwrite(path, content) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def declarations(kind)
|
|
49
|
+
@translation_unit.cursor.each.filter_map do |cursor, _parent|
|
|
50
|
+
cursor if cursor.kind == kind && cursor.location.file.to_s == HEADER
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def records
|
|
55
|
+
declarations(:cursor_struct)
|
|
56
|
+
.select { |cursor| cursor.definition? && !cursor.spelling.empty? }
|
|
57
|
+
.uniq(&:spelling)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def functions
|
|
61
|
+
declarations(:cursor_function).uniq(&:spelling)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fields(record)
|
|
65
|
+
record.each(false).filter_map do |cursor, _parent|
|
|
66
|
+
cursor if cursor.kind == :cursor_field_decl
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def sorted_records
|
|
71
|
+
remaining = records.to_h { |record| [record.spelling, record] }
|
|
72
|
+
sorted = []
|
|
73
|
+
until remaining.empty?
|
|
74
|
+
ready = remaining.values.select do |record|
|
|
75
|
+
record_dependencies(record).none? { |name| remaining.key?(name) }
|
|
76
|
+
end
|
|
77
|
+
abort "cyclic struct dependency: #{remaining.keys.join(", ")}" if ready.empty?
|
|
78
|
+
|
|
79
|
+
ready.sort_by(&:spelling).each do |record|
|
|
80
|
+
sorted << record
|
|
81
|
+
remaining.delete(record.spelling)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
sorted
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def record_dependencies(record)
|
|
88
|
+
fields(record).filter_map do |field|
|
|
89
|
+
type = field.type.canonical
|
|
90
|
+
type = type.element_type.canonical if type.kind == :type_constant_array
|
|
91
|
+
type.spelling if type.kind == :type_record && type.spelling != record.spelling
|
|
92
|
+
end.uniq
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def render_ruby
|
|
96
|
+
lines = [
|
|
97
|
+
"# frozen_string_literal: true",
|
|
98
|
+
"",
|
|
99
|
+
"# Generated from ext/joltc/include/joltc.h by generator/generate.rb.",
|
|
100
|
+
"# Do not edit this file directly.",
|
|
101
|
+
"",
|
|
102
|
+
"require \"ffi\"",
|
|
103
|
+
"",
|
|
104
|
+
"module Jolt",
|
|
105
|
+
" module Native",
|
|
106
|
+
" module Generated"
|
|
107
|
+
]
|
|
108
|
+
sorted_records.each { |record| render_record(lines, record) }
|
|
109
|
+
render_function_table(lines)
|
|
110
|
+
render_layout_table(lines)
|
|
111
|
+
lines.concat [
|
|
112
|
+
"",
|
|
113
|
+
" BLOCKING_FUNCTIONS = %i[JPH_PhysicsSystem_Update].freeze",
|
|
114
|
+
"",
|
|
115
|
+
" module_function",
|
|
116
|
+
"",
|
|
117
|
+
" def attach(native, names: FUNCTIONS.keys)",
|
|
118
|
+
" missing = []",
|
|
119
|
+
" names.each do |name|",
|
|
120
|
+
" arguments, result = FUNCTIONS.fetch(name)",
|
|
121
|
+
" options = BLOCKING_FUNCTIONS.include?(name) ? { blocking: true } : {}",
|
|
122
|
+
" begin",
|
|
123
|
+
" native.attach_function(name, arguments, result, **options)",
|
|
124
|
+
" rescue FFI::NotFoundError",
|
|
125
|
+
" missing << name",
|
|
126
|
+
" end",
|
|
127
|
+
" end",
|
|
128
|
+
" @missing_functions = missing.freeze",
|
|
129
|
+
" end",
|
|
130
|
+
"",
|
|
131
|
+
" def missing_functions",
|
|
132
|
+
" @missing_functions || [].freeze",
|
|
133
|
+
" end",
|
|
134
|
+
" end",
|
|
135
|
+
" end",
|
|
136
|
+
"end",
|
|
137
|
+
""
|
|
138
|
+
]
|
|
139
|
+
lines.join("\n")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def render_record(lines, record)
|
|
143
|
+
lines << ""
|
|
144
|
+
lines << " class #{ruby_class_name(record.spelling)} < FFI::Struct"
|
|
145
|
+
layout = fields(record).flat_map do |field|
|
|
146
|
+
[":#{snake_case(field.spelling)}", ruby_type(field.type, field: true)]
|
|
147
|
+
end
|
|
148
|
+
if layout.empty?
|
|
149
|
+
lines << " end"
|
|
150
|
+
return
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
lines << " layout #{layout.each_slice(2).map { |pair| pair.join(", ") }.join(",\n ")}"
|
|
154
|
+
lines << " end"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def render_function_table(lines)
|
|
158
|
+
lines << ""
|
|
159
|
+
lines << " FUNCTIONS = {"
|
|
160
|
+
functions.sort_by(&:spelling).each do |function|
|
|
161
|
+
arguments = function.num_arguments.times.map do |index|
|
|
162
|
+
ruby_type(function.argument(index).type, argument: true)
|
|
163
|
+
end
|
|
164
|
+
result = ruby_type(function.result_type)
|
|
165
|
+
lines << " #{function.spelling}: [[#{arguments.join(", ")}], #{result}],"
|
|
166
|
+
end
|
|
167
|
+
lines << " }.freeze"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def render_layout_table(lines)
|
|
171
|
+
lines << ""
|
|
172
|
+
lines << " HEADER_LAYOUTS = {"
|
|
173
|
+
sorted_records.each do |record|
|
|
174
|
+
offsets = fields(record).map do |field|
|
|
175
|
+
"#{snake_case(field.spelling)}: #{field.offset_of_field / 8}"
|
|
176
|
+
end
|
|
177
|
+
lines << " #{record.spelling.inspect} => [#{record.type.sizeof}, { #{offsets.join(", ")} }],"
|
|
178
|
+
end
|
|
179
|
+
lines << " }.freeze"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def render_probe
|
|
183
|
+
lines = [
|
|
184
|
+
"// Generated from ext/joltc/include/joltc.h by generator/generate.rb.",
|
|
185
|
+
"// Do not edit this file directly.",
|
|
186
|
+
"#include <cstddef>",
|
|
187
|
+
"#include <cstdio>",
|
|
188
|
+
"#include <joltc.h>",
|
|
189
|
+
"",
|
|
190
|
+
"int main() {"
|
|
191
|
+
]
|
|
192
|
+
sorted_records.each do |record|
|
|
193
|
+
lines << %( std::printf("S\\t#{record.spelling}\\t%zu\\n", sizeof(#{record.spelling}));)
|
|
194
|
+
fields(record).each do |field|
|
|
195
|
+
lines << %( std::printf("F\\t#{record.spelling}\\t#{snake_case(field.spelling)}\\t%zu\\n", offsetof(#{record.spelling}, #{field.spelling}));)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
lines.concat [" return 0;", "}", ""]
|
|
199
|
+
lines.join("\n")
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def ruby_type(type, field: false, argument: false)
|
|
203
|
+
canonical = type.canonical
|
|
204
|
+
kind = canonical.kind
|
|
205
|
+
return ":pointer" if argument && kind == :type_constant_array
|
|
206
|
+
return ":pointer" if kind == :type_pointer
|
|
207
|
+
return ":void" if kind == :type_void
|
|
208
|
+
return ":bool" if kind == :type_bool
|
|
209
|
+
return ":float" if kind == :type_float
|
|
210
|
+
return ":double" if kind == :type_double
|
|
211
|
+
return ":int" if kind == :type_enum
|
|
212
|
+
return INTEGER_TYPES.fetch(kind).inspect if INTEGER_TYPES.key?(kind)
|
|
213
|
+
if kind == :type_record
|
|
214
|
+
suffix = field || argument ? ".by_value" : ".by_value"
|
|
215
|
+
return "#{ruby_class_name(canonical.spelling)}#{suffix}"
|
|
216
|
+
end
|
|
217
|
+
if kind == :type_constant_array
|
|
218
|
+
return "[#{ruby_type(canonical.element_type, field: true)}, #{canonical.size}]"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
abort "unsupported C type: #{type.spelling} (#{kind})"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def ruby_class_name(c_name)
|
|
225
|
+
c_name.delete_prefix("JPH_")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def snake_case(name)
|
|
229
|
+
name
|
|
230
|
+
.sub(/\A_+/, "")
|
|
231
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
232
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
233
|
+
.downcase
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
BindingGenerator.new.run(check: ARGV.delete("--check"))
|