box2d-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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +336 -0
  4. data/examples/contact_events.rb +22 -0
  5. data/examples/debug_draw.rb +15 -0
  6. data/examples/falling_ball.rb +19 -0
  7. data/examples/rugl_debug_draw.rb +68 -0
  8. data/examples/stagecraft_debug_draw.rb +64 -0
  9. data/examples/support/box2d_debug_lines.rb +125 -0
  10. data/ext/box2d/CMakeLists.txt +35 -0
  11. data/ext/box2d/extconf.rb +42 -0
  12. data/ext/box2d/native.c +9 -0
  13. data/ext/box2d/vendor/box2d/LICENSE +21 -0
  14. data/ext/box2d/vendor/box2d/VERSION +1 -0
  15. data/ext/box2d/vendor/box2d/include/box2d/base.h +131 -0
  16. data/ext/box2d/vendor/box2d/include/box2d/box2d.h +1222 -0
  17. data/ext/box2d/vendor/box2d/include/box2d/collision.h +830 -0
  18. data/ext/box2d/vendor/box2d/include/box2d/id.h +144 -0
  19. data/ext/box2d/vendor/box2d/include/box2d/math_functions.h +761 -0
  20. data/ext/box2d/vendor/box2d/include/box2d/types.h +1457 -0
  21. data/ext/box2d/vendor/box2d/src/CMakeLists.txt +223 -0
  22. data/ext/box2d/vendor/box2d/src/aabb.c +132 -0
  23. data/ext/box2d/vendor/box2d/src/aabb.h +56 -0
  24. data/ext/box2d/vendor/box2d/src/arena_allocator.c +112 -0
  25. data/ext/box2d/vendor/box2d/src/arena_allocator.h +48 -0
  26. data/ext/box2d/vendor/box2d/src/array.c +8 -0
  27. data/ext/box2d/vendor/box2d/src/array.h +179 -0
  28. data/ext/box2d/vendor/box2d/src/atomic.h +79 -0
  29. data/ext/box2d/vendor/box2d/src/bitset.c +67 -0
  30. data/ext/box2d/vendor/box2d/src/bitset.h +65 -0
  31. data/ext/box2d/vendor/box2d/src/body.c +1884 -0
  32. data/ext/box2d/vendor/box2d/src/body.h +194 -0
  33. data/ext/box2d/vendor/box2d/src/box2d.natvis +41 -0
  34. data/ext/box2d/vendor/box2d/src/broad_phase.c +524 -0
  35. data/ext/box2d/vendor/box2d/src/broad_phase.h +83 -0
  36. data/ext/box2d/vendor/box2d/src/constants.h +54 -0
  37. data/ext/box2d/vendor/box2d/src/constraint_graph.c +322 -0
  38. data/ext/box2d/vendor/box2d/src/constraint_graph.h +58 -0
  39. data/ext/box2d/vendor/box2d/src/contact.c +650 -0
  40. data/ext/box2d/vendor/box2d/src/contact.h +148 -0
  41. data/ext/box2d/vendor/box2d/src/contact_solver.c +2120 -0
  42. data/ext/box2d/vendor/box2d/src/contact_solver.h +54 -0
  43. data/ext/box2d/vendor/box2d/src/core.c +178 -0
  44. data/ext/box2d/vendor/box2d/src/core.h +149 -0
  45. data/ext/box2d/vendor/box2d/src/ctz.h +112 -0
  46. data/ext/box2d/vendor/box2d/src/distance.c +1415 -0
  47. data/ext/box2d/vendor/box2d/src/distance_joint.c +556 -0
  48. data/ext/box2d/vendor/box2d/src/dynamic_tree.c +1989 -0
  49. data/ext/box2d/vendor/box2d/src/geometry.c +1028 -0
  50. data/ext/box2d/vendor/box2d/src/hull.c +328 -0
  51. data/ext/box2d/vendor/box2d/src/id_pool.c +79 -0
  52. data/ext/box2d/vendor/box2d/src/id_pool.h +35 -0
  53. data/ext/box2d/vendor/box2d/src/island.c +977 -0
  54. data/ext/box2d/vendor/box2d/src/island.h +89 -0
  55. data/ext/box2d/vendor/box2d/src/joint.c +1272 -0
  56. data/ext/box2d/vendor/box2d/src/joint.h +335 -0
  57. data/ext/box2d/vendor/box2d/src/manifold.c +1726 -0
  58. data/ext/box2d/vendor/box2d/src/math_functions.c +159 -0
  59. data/ext/box2d/vendor/box2d/src/motor_joint.c +283 -0
  60. data/ext/box2d/vendor/box2d/src/mouse_joint.c +214 -0
  61. data/ext/box2d/vendor/box2d/src/mover.c +73 -0
  62. data/ext/box2d/vendor/box2d/src/prismatic_joint.c +656 -0
  63. data/ext/box2d/vendor/box2d/src/revolute_joint.c +534 -0
  64. data/ext/box2d/vendor/box2d/src/sensor.c +389 -0
  65. data/ext/box2d/vendor/box2d/src/sensor.h +36 -0
  66. data/ext/box2d/vendor/box2d/src/shape.c +1714 -0
  67. data/ext/box2d/vendor/box2d/src/shape.h +123 -0
  68. data/ext/box2d/vendor/box2d/src/solver.c +2038 -0
  69. data/ext/box2d/vendor/box2d/src/solver.h +155 -0
  70. data/ext/box2d/vendor/box2d/src/solver_set.c +613 -0
  71. data/ext/box2d/vendor/box2d/src/solver_set.h +57 -0
  72. data/ext/box2d/vendor/box2d/src/table.c +238 -0
  73. data/ext/box2d/vendor/box2d/src/table.h +37 -0
  74. data/ext/box2d/vendor/box2d/src/timer.c +185 -0
  75. data/ext/box2d/vendor/box2d/src/types.c +151 -0
  76. data/ext/box2d/vendor/box2d/src/weld_joint.c +310 -0
  77. data/ext/box2d/vendor/box2d/src/wheel_joint.c +551 -0
  78. data/ext/box2d/vendor/box2d/src/world.c +3301 -0
  79. data/ext/box2d/vendor/box2d/src/world.h +192 -0
  80. data/generator/generate.rb +316 -0
  81. data/generator/layout_probe.c +507 -0
  82. data/generator/verify_layouts.rb +32 -0
  83. data/lib/box2d/body.rb +172 -0
  84. data/lib/box2d/body_definition.rb +38 -0
  85. data/lib/box2d/body_shapes.rb +135 -0
  86. data/lib/box2d/chain.rb +61 -0
  87. data/lib/box2d/debug_draw.rb +118 -0
  88. data/lib/box2d/events.rb +103 -0
  89. data/lib/box2d/fixed_stepper.rb +39 -0
  90. data/lib/box2d/handle.rb +46 -0
  91. data/lib/box2d/hit.rb +5 -0
  92. data/lib/box2d/joint.rb +197 -0
  93. data/lib/box2d/native.rb +1462 -0
  94. data/lib/box2d/native_loader.rb +45 -0
  95. data/lib/box2d/pixel_scale.rb +29 -0
  96. data/lib/box2d/shape.rb +109 -0
  97. data/lib/box2d/shape_definition.rb +44 -0
  98. data/lib/box2d/value_conversion.rb +80 -0
  99. data/lib/box2d/version.rb +7 -0
  100. data/lib/box2d/world.rb +135 -0
  101. data/lib/box2d/world_joints.rb +156 -0
  102. data/lib/box2d/world_queries.rb +122 -0
  103. data/lib/box2d/world_registry.rb +95 -0
  104. data/lib/box2d.rb +31 -0
  105. data/script/build_platform_gem.rb +24 -0
  106. data/script/deterministic_scene.rb +56 -0
  107. data/script/update_deterministic_snapshot.rb +11 -0
  108. data/script/verify_platform_gem.rb +24 -0
  109. metadata +164 -0
@@ -0,0 +1,192 @@
1
+ // SPDX-FileCopyrightText: 2023 Erin Catto
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ #pragma once
5
+
6
+ #include "array.h"
7
+ #include "bitset.h"
8
+ #include "broad_phase.h"
9
+ #include "constraint_graph.h"
10
+ #include "id_pool.h"
11
+ #include "arena_allocator.h"
12
+
13
+ #include "box2d/types.h"
14
+
15
+ enum b2SetType
16
+ {
17
+ b2_staticSet = 0,
18
+ b2_disabledSet = 1,
19
+ b2_awakeSet = 2,
20
+ b2_firstSleepingSet = 3,
21
+ };
22
+
23
+ // Per thread task storage
24
+ typedef struct b2TaskContext
25
+ {
26
+ // These bits align with the b2ConstraintGraph::contactBlocks and signal a change in contact status
27
+ b2BitSet contactStateBitSet;
28
+
29
+ // Used to track bodies with shapes that have enlarged AABBs. This avoids having a bit array
30
+ // that is very large when there are many static shapes.
31
+ b2BitSet enlargedSimBitSet;
32
+
33
+ // Used to put islands to sleep
34
+ b2BitSet awakeIslandBitSet;
35
+
36
+ // Per worker split island candidate
37
+ float splitSleepTime;
38
+ int splitIslandId;
39
+
40
+ } b2TaskContext;
41
+
42
+ // The world struct manages all physics entities, dynamic simulation, and asynchronous queries.
43
+ // The world also contains efficient memory management facilities.
44
+ typedef struct b2World
45
+ {
46
+ b2ArenaAllocator arena;
47
+ b2BroadPhase broadPhase;
48
+ b2ConstraintGraph constraintGraph;
49
+
50
+ // The body id pool is used to allocate and recycle body ids. Body ids
51
+ // provide a stable identifier for users, but incur caches misses when used
52
+ // to access body data. Aligns with b2Body.
53
+ b2IdPool bodyIdPool;
54
+
55
+ // This is a sparse array that maps body ids to the body data
56
+ // stored in solver sets. As sims move within a set or across set.
57
+ // Indices come from id pool.
58
+ b2BodyArray bodies;
59
+
60
+ // Provides free list for solver sets.
61
+ b2IdPool solverSetIdPool;
62
+
63
+ // Solvers sets allow sims to be stored in contiguous arrays. The first
64
+ // set is all static sims. The second set is active sims. The third set is disabled
65
+ // sims. The remaining sets are sleeping islands.
66
+ b2SolverSetArray solverSets;
67
+
68
+ // Used to create stable ids for joints
69
+ b2IdPool jointIdPool;
70
+
71
+ // This is a sparse array that maps joint ids to the joint data stored in the constraint graph
72
+ // or in the solver sets.
73
+ b2JointArray joints;
74
+
75
+ // Used to create stable ids for contacts
76
+ b2IdPool contactIdPool;
77
+
78
+ // This is a sparse array that maps contact ids to the contact data stored in the constraint graph
79
+ // or in the solver sets.
80
+ b2ContactArray contacts;
81
+
82
+ // Used to create stable ids for islands
83
+ b2IdPool islandIdPool;
84
+
85
+ // This is a sparse array that maps island ids to the island data stored in the solver sets.
86
+ b2IslandArray islands;
87
+
88
+ b2IdPool shapeIdPool;
89
+ b2IdPool chainIdPool;
90
+
91
+ // These are sparse arrays that point into the pools above
92
+ b2ShapeArray shapes;
93
+ b2ChainShapeArray chainShapes;
94
+
95
+ // This is a dense array of sensor data.
96
+ b2SensorArray sensors;
97
+
98
+ // Per thread storage
99
+ b2TaskContextArray taskContexts;
100
+ b2SensorTaskContextArray sensorTaskContexts;
101
+
102
+ b2BodyMoveEventArray bodyMoveEvents;
103
+ b2SensorBeginTouchEventArray sensorBeginEvents;
104
+ b2ContactBeginTouchEventArray contactBeginEvents;
105
+
106
+ // End events are double buffered so that the user doesn't need to flush events
107
+ b2SensorEndTouchEventArray sensorEndEvents[2];
108
+ b2ContactEndTouchEventArray contactEndEvents[2];
109
+ int endEventArrayIndex;
110
+
111
+ b2ContactHitEventArray contactHitEvents;
112
+
113
+ // Used to track debug draw
114
+ b2BitSet debugBodySet;
115
+ b2BitSet debugJointSet;
116
+ b2BitSet debugContactSet;
117
+ b2BitSet debugIslandSet;
118
+
119
+ // Id that is incremented every time step
120
+ uint64_t stepIndex;
121
+
122
+ // Identify islands for splitting as follows:
123
+ // - I want to split islands so smaller islands can sleep
124
+ // - when a body comes to rest and its sleep timer trips, I can look at the island and flag it for splitting
125
+ // if it has removed constraints
126
+ // - islands that have removed constraints must be put split first because I don't want to wake bodies incorrectly
127
+ // - otherwise I can use the awake islands that have bodies wanting to sleep as the splitting candidates
128
+ // - if no bodies want to sleep then there is no reason to perform island splitting
129
+ int splitIslandId;
130
+
131
+ b2Vec2 gravity;
132
+ float hitEventThreshold;
133
+ float restitutionThreshold;
134
+ float maxLinearSpeed;
135
+ float maxContactPushSpeed;
136
+ float contactHertz;
137
+ float contactDampingRatio;
138
+ float jointHertz;
139
+ float jointDampingRatio;
140
+
141
+ b2FrictionCallback* frictionCallback;
142
+ b2RestitutionCallback* restitutionCallback;
143
+
144
+ uint16_t generation;
145
+
146
+ b2Profile profile;
147
+
148
+ b2PreSolveFcn* preSolveFcn;
149
+ void* preSolveContext;
150
+
151
+ b2CustomFilterFcn* customFilterFcn;
152
+ void* customFilterContext;
153
+
154
+ int workerCount;
155
+ b2EnqueueTaskCallback* enqueueTaskFcn;
156
+ b2FinishTaskCallback* finishTaskFcn;
157
+ void* userTaskContext;
158
+ void* userTreeTask;
159
+
160
+ void* userData;
161
+
162
+ // Remember type step used for reporting forces and torques
163
+ float inv_h;
164
+
165
+ int activeTaskCount;
166
+ int taskCount;
167
+
168
+ uint16_t worldId;
169
+
170
+ bool enableSleep;
171
+ bool locked;
172
+ bool enableWarmStarting;
173
+ bool enableContinuous;
174
+ bool enableSpeculative;
175
+ bool inUse;
176
+ } b2World;
177
+
178
+ b2World* b2GetWorldFromId( b2WorldId id );
179
+ b2World* b2GetWorld( int index );
180
+ b2World* b2GetWorldLocked( int index );
181
+
182
+ void b2ValidateConnectivity( b2World* world );
183
+ void b2ValidateSolverSets( b2World* world );
184
+ void b2ValidateContacts( b2World* world );
185
+
186
+ B2_ARRAY_INLINE( b2BodyMoveEvent, b2BodyMoveEvent )
187
+ B2_ARRAY_INLINE( b2ContactBeginTouchEvent, b2ContactBeginTouchEvent )
188
+ B2_ARRAY_INLINE( b2ContactEndTouchEvent, b2ContactEndTouchEvent )
189
+ B2_ARRAY_INLINE( b2ContactHitEvent, b2ContactHitEvent )
190
+ B2_ARRAY_INLINE( b2SensorBeginTouchEvent, b2SensorBeginTouchEvent )
191
+ B2_ARRAY_INLINE( b2SensorEndTouchEvent, b2SensorEndTouchEvent )
192
+ B2_ARRAY_INLINE( b2TaskContext, b2TaskContext )
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi/clang"
4
+
5
+ module Box2DBindings
6
+ ROOT = File.expand_path("..", __dir__)
7
+ INCLUDE_ROOT = File.join(ROOT, "ext/box2d/vendor/box2d/include")
8
+ HEADER = File.join(INCLUDE_ROOT, "box2d/box2d.h")
9
+ OUTPUT = File.join(ROOT, "lib/box2d/native.rb")
10
+ PROBE_OUTPUT = File.join(__dir__, "layout_probe.c")
11
+
12
+ Field = Data.define(:name, :type, :offset)
13
+ Struct = Data.define(:name, :ruby_name, :size, :alignment, :fields)
14
+ Enum = Data.define(:name, :ruby_name, :values)
15
+ Function = Data.define(:name, :arguments, :result)
16
+
17
+ class TypeMapper
18
+ FIXED_TYPES = {
19
+ "int8_t" => ":int8",
20
+ "uint8_t" => ":uint8",
21
+ "int16_t" => ":int16",
22
+ "uint16_t" => ":uint16",
23
+ "int32_t" => ":int32",
24
+ "uint32_t" => ":uint32",
25
+ "int64_t" => ":int64",
26
+ "uint64_t" => ":uint64",
27
+ "size_t" => ":size_t"
28
+ }.freeze
29
+
30
+ PRIMITIVE_TYPES = {
31
+ type_void: ":void",
32
+ type_bool: ":bool",
33
+ type_char_s: ":char",
34
+ type_schar: ":int8",
35
+ type_uchar: ":uint8",
36
+ type_short: ":short",
37
+ type_ushort: ":ushort",
38
+ type_int: ":int",
39
+ type_uint: ":uint",
40
+ type_long: ":long",
41
+ type_ulong: ":ulong",
42
+ type_longlong: ":long_long",
43
+ type_ulonglong: ":ulong_long",
44
+ type_float: ":float",
45
+ type_double: ":double"
46
+ }.freeze
47
+
48
+ def field(type)
49
+ map(type, struct_by_value: true)
50
+ end
51
+
52
+ def parameter(type)
53
+ map(type, struct_by_value: true)
54
+ end
55
+
56
+ private
57
+
58
+ def map(type, struct_by_value:)
59
+ spelling = type.spelling.sub(/\Aconst\s+/, "")
60
+ return FIXED_TYPES.fetch(spelling) if FIXED_TYPES.key?(spelling)
61
+ return ":pointer" if type.kind == :type_pointer
62
+ return array(type) if type.kind == :type_constant_array
63
+
64
+ canonical = canonical_type(type)
65
+ primitive = PRIMITIVE_TYPES[canonical.kind]
66
+ return primitive if primitive
67
+ return ":int" if canonical.kind == :type_enum
68
+
69
+ if canonical.kind == :type_record
70
+ class_name = ruby_name(canonical.declaration.spelling)
71
+ return struct_by_value ? "#{class_name}.by_value" : class_name
72
+ end
73
+
74
+ raise "unsupported C type #{type.spelling.inspect} (#{type.kind}, canonical #{canonical.kind})"
75
+ end
76
+
77
+ def canonical_type(type)
78
+ return type.canonical if %i[type_elaborated type_typedef].include?(type.kind)
79
+
80
+ type
81
+ end
82
+
83
+ def array(type)
84
+ "[#{field(type.element_type)}, #{type.size}]"
85
+ end
86
+
87
+ def ruby_name(name)
88
+ name.delete_prefix("b2")
89
+ end
90
+ end
91
+
92
+ class Generator
93
+ def initialize
94
+ @mapper = TypeMapper.new
95
+ @translation_unit = parse
96
+ @structs = []
97
+ @enums = []
98
+ @functions = []
99
+ end
100
+
101
+ def run(check:)
102
+ collect_declarations
103
+ write_or_check(OUTPUT, render_bindings, check:)
104
+ write_or_check(PROBE_OUTPUT, render_probe, check:)
105
+ end
106
+
107
+ private
108
+
109
+ def parse
110
+ index = FFI::Clang::Index.new
111
+ unit = index.parse_translation_unit(
112
+ HEADER,
113
+ ["-x", "c", "-std=c17", "-I#{INCLUDE_ROOT}"]
114
+ )
115
+ errors = unit.diagnostics.select { |diagnostic| %i[error fatal].include?(diagnostic.severity) }
116
+ abort errors.map(&:spelling).join("\n") unless errors.empty?
117
+
118
+ unit
119
+ end
120
+
121
+ def collect_declarations
122
+ api_names = exported_function_names
123
+
124
+ @translation_unit.cursor.each(false) do |cursor, _parent|
125
+ case cursor.kind
126
+ when :cursor_struct
127
+ collect_struct(cursor) if box2d_declaration?(cursor)
128
+ when :cursor_enum_decl
129
+ collect_enum(cursor) if box2d_declaration?(cursor)
130
+ when :cursor_function
131
+ collect_function(cursor) if api_names.include?(cursor.spelling)
132
+ end
133
+ :continue
134
+ end
135
+ end
136
+
137
+ def collect_struct(cursor)
138
+ fields = []
139
+ cursor.each(false) do |field, _parent|
140
+ next :continue unless field.kind == :cursor_field_decl
141
+
142
+ fields << Field.new(
143
+ name: field.spelling,
144
+ type: @mapper.field(field.type),
145
+ offset: field.offset_of_field / 8
146
+ )
147
+ :continue
148
+ end
149
+ return if fields.empty?
150
+
151
+ @structs << Struct.new(
152
+ name: cursor.spelling,
153
+ ruby_name: ruby_name(cursor.spelling),
154
+ size: cursor.type.sizeof,
155
+ alignment: cursor.type.alignof,
156
+ fields:
157
+ )
158
+ end
159
+
160
+ def collect_enum(cursor)
161
+ values = {}
162
+ cursor.each(false) do |constant, _parent|
163
+ next :continue unless constant.kind == :cursor_enum_constant_decl
164
+
165
+ values[enum_constant_name(constant.spelling)] = constant.enum_value
166
+ :continue
167
+ end
168
+ return if values.empty?
169
+
170
+ @enums << Enum.new(
171
+ name: cursor.spelling,
172
+ ruby_name: ruby_name(cursor.spelling),
173
+ values:
174
+ )
175
+ end
176
+
177
+ def collect_function(cursor)
178
+ arguments = cursor.type.arg_types.map { |type| @mapper.parameter(type) }
179
+ @functions << Function.new(
180
+ name: cursor.spelling,
181
+ arguments:,
182
+ result: @mapper.parameter(cursor.result_type)
183
+ )
184
+ end
185
+
186
+ def box2d_declaration?(cursor)
187
+ cursor.spelling.start_with?("b2") && cursor.location.file.to_s.start_with?(INCLUDE_ROOT)
188
+ end
189
+
190
+ def exported_function_names
191
+ Dir[File.join(INCLUDE_ROOT, "box2d/*.h")].flat_map do |header|
192
+ File.read(header).scan(/\bB2_API\s+[^;{]+?\b(b2[A-Za-z0-9_]+)\s*\(/m).flatten
193
+ end.to_h { |name| [name, true] }
194
+ end
195
+
196
+ def render_bindings
197
+ <<~RUBY
198
+ # frozen_string_literal: true
199
+
200
+ # Generated by generator/generate.rb from Box2D #{box2d_version} headers.
201
+ # Do not edit this file directly.
202
+
203
+ require "ffi"
204
+ require_relative "native_loader"
205
+
206
+ module Box2D
207
+ module Native
208
+ extend FFI::Library
209
+
210
+ ffi_lib NativeLoader.library_path
211
+
212
+ #{render_enums}
213
+ #{render_structs}
214
+ ABI_LAYOUTS = #{render_layout_hash}.freeze
215
+
216
+ #{render_functions}
217
+ end
218
+ end
219
+ RUBY
220
+ end
221
+
222
+ def render_enums
223
+ @enums.map do |enum|
224
+ constants = enum.values.map { |name, value| " #{name} = #{value}" }.join("\n")
225
+ <<~RUBY.chomp
226
+ module #{enum.ruby_name}
227
+ #{constants}
228
+ end
229
+ RUBY
230
+ end.join("\n\n")
231
+ end
232
+
233
+ def render_structs
234
+ @structs.map do |struct|
235
+ fields = struct.fields.flat_map { |field| [":#{field.name}", field.type] }
236
+ layout = fields.each_slice(2).map.with_index do |(name, type), index|
237
+ suffix = index == (fields.length / 2) - 1 ? "" : ","
238
+ " #{name}, #{type}#{suffix}"
239
+ end.join("\n")
240
+ <<~RUBY.chomp
241
+ class #{struct.ruby_name} < FFI::Struct
242
+ layout(
243
+ #{layout}
244
+ )
245
+ end
246
+ RUBY
247
+ end.join("\n\n")
248
+ end
249
+
250
+ def render_layout_hash
251
+ lines = @structs.map do |struct|
252
+ offsets = struct.fields.to_h { |field| [field.name.to_sym, field.offset] }
253
+ "#{struct.ruby_name.inspect} => {size: #{struct.size}, alignment: #{struct.alignment}, offsets: #{offsets.inspect}}"
254
+ end
255
+ "{\n #{lines.join(",\n ")}\n }"
256
+ end
257
+
258
+ def render_functions
259
+ @functions.map do |function|
260
+ options = function.name == "b2World_Step" ? ", blocking: true" : ""
261
+ " attach_function :#{function.name}, [#{function.arguments.join(", ")}], #{function.result}#{options}"
262
+ end.join("\n")
263
+ end
264
+
265
+ def render_probe
266
+ struct_lines = @structs.flat_map do |struct|
267
+ [
268
+ %(printf("S\\t#{struct.name}\\t%zu\\t%zu\\n", sizeof(#{struct.name}), _Alignof(#{struct.name}));),
269
+ *struct.fields.map do |field|
270
+ %(printf("F\\t#{struct.name}\\t#{field.name}\\t%zu\\n", offsetof(#{struct.name}, #{field.name}));)
271
+ end
272
+ ]
273
+ end
274
+
275
+ <<~C
276
+ // Generated by generator/generate.rb. Do not edit.
277
+ #include <stddef.h>
278
+ #include <stdio.h>
279
+ #include <box2d/box2d.h>
280
+
281
+ int main(void)
282
+ {
283
+ #{struct_lines.map { |line| " #{line}" }.join("\n")}
284
+ return 0;
285
+ }
286
+ C
287
+ end
288
+
289
+ def write_or_check(path, content, check:)
290
+ if check
291
+ abort "#{path.delete_prefix("#{ROOT}/")} is out of date" unless File.file?(path) && File.read(path) == content
292
+ return
293
+ end
294
+
295
+ File.write(path, content)
296
+ puts "generated #{path.delete_prefix("#{ROOT}/")}"
297
+ end
298
+
299
+ def ruby_name(name)
300
+ name.delete_prefix("b2")
301
+ end
302
+
303
+ def enum_constant_name(name)
304
+ name
305
+ .delete_prefix("b2_")
306
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
307
+ .upcase
308
+ end
309
+
310
+ def box2d_version
311
+ File.read(File.join(ROOT, "ext/box2d/vendor/box2d/VERSION")).strip
312
+ end
313
+ end
314
+ end
315
+
316
+ Box2DBindings::Generator.new.run(check: ARGV.delete("--check"))