rgame 0.1.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/LICENSE +26 -0
- data/README.md +406 -0
- data/docs/api/README.md +167 -0
- data/docs/api/app.md +192 -0
- data/docs/api/assets.md +426 -0
- data/docs/api/audio.md +208 -0
- data/docs/api/components.md +321 -0
- data/docs/api/drawing.md +330 -0
- data/docs/api/game.md +99 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +179 -0
- data/docs/api/internals.md +110 -0
- data/docs/api/scene_graph.md +159 -0
- data/docs/api/signals.md +142 -0
- data/docs/api/systems.md +98 -0
- data/docs/api/text.md +116 -0
- data/docs/api/toolbox.md +240 -0
- data/docs/api/values.md +101 -0
- data/ext/README.md +225 -0
- data/ext/rgame_core/app/app.c +721 -0
- data/ext/rgame_core/app/app_gl.h +64 -0
- data/ext/rgame_core/app/frame_loop.c +42 -0
- data/ext/rgame_core/app/frame_loop.h +54 -0
- data/ext/rgame_core/audio/audio.c +466 -0
- data/ext/rgame_core/audio/audio_internal.h +45 -0
- data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
- data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
- data/ext/rgame_core/example.rb +188 -0
- data/ext/rgame_core/extconf.rb +167 -0
- data/ext/rgame_core/graphics/backend.c +52 -0
- data/ext/rgame_core/graphics/backend.h +64 -0
- data/ext/rgame_core/graphics/canvas.c +247 -0
- data/ext/rgame_core/graphics/canvas.h +143 -0
- data/ext/rgame_core/graphics/clip.c +87 -0
- data/ext/rgame_core/graphics/clip.h +89 -0
- data/ext/rgame_core/graphics/draw_queue.c +216 -0
- data/ext/rgame_core/graphics/draw_queue.h +174 -0
- data/ext/rgame_core/graphics/gl_backend.c +122 -0
- data/ext/rgame_core/graphics/gl_backend.h +43 -0
- data/ext/rgame_core/graphics/image.c +304 -0
- data/ext/rgame_core/graphics/image_internal.h +30 -0
- data/ext/rgame_core/graphics/primitives.c +189 -0
- data/ext/rgame_core/graphics/primitives.h +111 -0
- data/ext/rgame_core/graphics/recording.c +119 -0
- data/ext/rgame_core/graphics/recording.h +88 -0
- data/ext/rgame_core/graphics/texture.c +181 -0
- data/ext/rgame_core/graphics/texture.h +165 -0
- data/ext/rgame_core/graphics/transform.c +128 -0
- data/ext/rgame_core/graphics/transform.h +106 -0
- data/ext/rgame_core/include/rgame/core.h +577 -0
- data/ext/rgame_core/input/device_slots.c +103 -0
- data/ext/rgame_core/input/device_slots.h +93 -0
- data/ext/rgame_core/input/gamepad.c +145 -0
- data/ext/rgame_core/input/gamepad.h +63 -0
- data/ext/rgame_core/input/input.c +109 -0
- data/ext/rgame_core/input/input.h +99 -0
- data/ext/rgame_core/ruby/audio_ext.c +321 -0
- data/ext/rgame_core/ruby/core_ext.c +513 -0
- data/ext/rgame_core/ruby/core_ext.h +51 -0
- data/ext/rgame_core/ruby/font_ext.c +168 -0
- data/ext/rgame_core/ruby/image_ext.c +230 -0
- data/ext/rgame_core/ruby/recording_ext.c +186 -0
- data/ext/rgame_core/ruby/renderer_ext.c +376 -0
- data/ext/rgame_core/text/atlas.c +59 -0
- data/ext/rgame_core/text/atlas.h +85 -0
- data/ext/rgame_core/text/font.c +281 -0
- data/ext/rgame_core/text/font.h +139 -0
- data/ext/rgame_core/text/font_atlas.c +385 -0
- data/ext/rgame_core/text/font_internal.h +47 -0
- data/ext/rgame_core/text/glyph_cache.c +142 -0
- data/ext/rgame_core/text/glyph_cache.h +89 -0
- data/ext/rgame_core/vendor/README.md +159 -0
- data/ext/rgame_core/vendor/miniaudio.h +95864 -0
- data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
- data/ext/rgame_core/vendor/stb_image.h +7988 -0
- data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
- data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
- data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
- data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
- data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
- data/ext/rgame_util/color.c +19 -0
- data/ext/rgame_util/color.h +60 -0
- data/ext/rgame_util/color_ext.c +156 -0
- data/ext/rgame_util/extconf.rb +27 -0
- data/ext/rgame_util/tensor.c +186 -0
- data/ext/rgame_util/util_ext.c +27 -0
- data/ext/rgame_util/util_ext.h +16 -0
- data/lib/rgame/boot.rb +13 -0
- data/lib/rgame/core/app.rb +82 -0
- data/lib/rgame/core/asset_manager.rb +224 -0
- data/lib/rgame/core/audio.rb +124 -0
- data/lib/rgame/core/font.rb +49 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +77 -0
- data/lib/rgame/core/nine_slice.rb +163 -0
- data/lib/rgame/core/recording.rb +52 -0
- data/lib/rgame/core/renderer.rb +363 -0
- data/lib/rgame/core/sprite_sheet.rb +108 -0
- data/lib/rgame/core/tile_map_renderer.rb +160 -0
- data/lib/rgame/core/ui_atlas.rb +86 -0
- data/lib/rgame/core.rb +24 -0
- data/lib/rgame/engine/actor.rb +53 -0
- data/lib/rgame/engine/animation_set.rb +49 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +29 -0
- data/lib/rgame/engine/body.rb +49 -0
- data/lib/rgame/engine/cached_label.rb +33 -0
- data/lib/rgame/engine/camera.rb +33 -0
- data/lib/rgame/engine/camera_view.rb +28 -0
- data/lib/rgame/engine/circle_collider.rb +32 -0
- data/lib/rgame/engine/collision_box.rb +34 -0
- data/lib/rgame/engine/collision_system.rb +44 -0
- data/lib/rgame/engine/component.rb +30 -0
- data/lib/rgame/engine/components/action_trigger.rb +41 -0
- data/lib/rgame/engine/components/animated_sprite.rb +63 -0
- data/lib/rgame/engine/components/character_body.rb +70 -0
- data/lib/rgame/engine/components/circle_collider.rb +44 -0
- data/lib/rgame/engine/components/collision_world.rb +103 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
- data/lib/rgame/engine/components/path_follow.rb +84 -0
- data/lib/rgame/engine/components/player_controller.rb +24 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +27 -0
- data/lib/rgame/engine/components/sprite.rb +31 -0
- data/lib/rgame/engine/components/targeting.rb +54 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +68 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +27 -0
- data/lib/rgame/engine/components/wander_controller.rb +60 -0
- data/lib/rgame/engine/debug_overlay.rb +106 -0
- data/lib/rgame/engine/i18n.rb +97 -0
- data/lib/rgame/engine/input/action_mapper.rb +46 -0
- data/lib/rgame/engine/input/actions.rb +41 -0
- data/lib/rgame/engine/input/player_controller.rb +14 -0
- data/lib/rgame/engine/matrix.rb +32 -0
- data/lib/rgame/engine/node2d.rb +271 -0
- data/lib/rgame/engine/path.rb +78 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/resettable.rb +67 -0
- data/lib/rgame/engine/scene/scene_stack.rb +65 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +71 -0
- data/lib/rgame/engine/tile_collision.rb +78 -0
- data/lib/rgame/engine/tile_map.rb +149 -0
- data/lib/rgame/engine/tileset.rb +101 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine.rb +68 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +129 -0
- data/lib/rgame/util/color.rb +27 -0
- data/lib/rgame/util/controls.rb +107 -0
- data/lib/rgame/util/tensor.rb +12 -0
- data/lib/rgame/util.rb +8 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +20 -0
- metadata +215 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* core_ext.c — the Ruby C extension glue for the rgame core engine.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY file where Ruby-extension concerns (ruby.h, VALUE, the GC)
|
|
5
|
+
* meet the engine. Like src/main.c, it talks exclusively to the public API in
|
|
6
|
+
* rgame/core.h and never reaches into the opaque rgame_app struct. src/main.c
|
|
7
|
+
* drives the engine from a C main(); this file drives the same seams from
|
|
8
|
+
* Ruby, which is the whole point of keeping SDL/GL out of core.h.
|
|
9
|
+
*
|
|
10
|
+
* Everything here lands under RGame::Core — the namespace for code that
|
|
11
|
+
* depends on SDL/OpenGL. Anything that doesn't belongs in RGame::Util instead
|
|
12
|
+
* (ext/rgame_util/), which links no graphics libraries at all.
|
|
13
|
+
*
|
|
14
|
+
* App is designed to be *subclassed*: the engine calls back into methods on
|
|
15
|
+
* the object itself, so a game overrides the ones it cares about and inherits
|
|
16
|
+
* no-ops for the rest.
|
|
17
|
+
*
|
|
18
|
+
* class MyGame < RGame::Core::App
|
|
19
|
+
* def initialize = super(width: 800, height: 600, caption: 'demo')
|
|
20
|
+
* def update(dt); end # one fixed simulation tick
|
|
21
|
+
* def draw; end # render one frame
|
|
22
|
+
* def needs_redraw?; end # false skips the draw
|
|
23
|
+
* def button_down(id); end # discrete key press
|
|
24
|
+
* def button_up(id); end
|
|
25
|
+
* def frame_begin; end # once per frame, before the tick batch
|
|
26
|
+
* def resize(w, h); end
|
|
27
|
+
* def gamepad_connected(slot); end
|
|
28
|
+
* def gamepad_disconnected(slot); end
|
|
29
|
+
* end
|
|
30
|
+
*
|
|
31
|
+
* MyGame.new.run
|
|
32
|
+
*
|
|
33
|
+
* Inherited from App: #close, #width, #height, #caption, #caption=,
|
|
34
|
+
* #ticks_ms, #fps.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
#include <ruby.h>
|
|
38
|
+
|
|
39
|
+
#include "ruby/core_ext.h"
|
|
40
|
+
#include "rgame/core.h"
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* Cached interned identifiers. rb_intern turns a name into an ID (Ruby's
|
|
44
|
+
* interned-symbol integer); doing it once at load time instead of on every
|
|
45
|
+
* callback keeps the per-frame path cheap.
|
|
46
|
+
*/
|
|
47
|
+
static ID id_frame_begin;
|
|
48
|
+
static ID id_update;
|
|
49
|
+
static ID id_draw;
|
|
50
|
+
static ID id_needs_redraw;
|
|
51
|
+
static ID id_button_down;
|
|
52
|
+
static ID id_button_up;
|
|
53
|
+
static ID id_resize;
|
|
54
|
+
static ID id_gamepad_connected;
|
|
55
|
+
static ID id_gamepad_disconnected;
|
|
56
|
+
static ID id_width;
|
|
57
|
+
static ID id_height;
|
|
58
|
+
static ID id_caption;
|
|
59
|
+
static ID id_media_root;
|
|
60
|
+
|
|
61
|
+
/* ------------------------------------------------------------------------- *
|
|
62
|
+
* rgame_app lifetime, wrapped as a Ruby object
|
|
63
|
+
* ------------------------------------------------------------------------- */
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* TypedData is Ruby's mechanism for letting a Ruby object own a raw C pointer.
|
|
67
|
+
* We give it a "free" function so that when the Ruby App object is garbage
|
|
68
|
+
* collected, Ruby tears down the SDL window / GL context for us by calling the
|
|
69
|
+
* public destroy function. rgame_app_destroy is NULL-safe, so an App that was
|
|
70
|
+
* allocated but never initialized frees cleanly too.
|
|
71
|
+
*/
|
|
72
|
+
static void app_free(void *ptr) {
|
|
73
|
+
rgame_app_destroy((rgame_app *)ptr);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static const rb_data_type_t app_data_type = {
|
|
77
|
+
.wrap_struct_name = "rgame_app",
|
|
78
|
+
.function = {
|
|
79
|
+
.dmark = NULL, /* nothing Ruby-visible to mark inside the C struct */
|
|
80
|
+
.dfree = app_free,
|
|
81
|
+
.dsize = NULL,
|
|
82
|
+
},
|
|
83
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/* Pull the C pointer back out of the Ruby object, raising if it's not set up.
|
|
87
|
+
* Also the accessor other files in this extension use (see core_ext.h), which
|
|
88
|
+
* is why it is not static: TypedData_Get_Struct raises TypeError on anything
|
|
89
|
+
* that is not an App, so callers get that check for free. */
|
|
90
|
+
rgame_app *rgame_app_unwrap(VALUE self) {
|
|
91
|
+
rgame_app *app;
|
|
92
|
+
TypedData_Get_Struct(self, rgame_app, &app_data_type, app);
|
|
93
|
+
if (!app) {
|
|
94
|
+
rb_raise(rb_eRuntimeError, "rgame app is not initialized");
|
|
95
|
+
}
|
|
96
|
+
return app;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* alloc/initialize are split the Ruby way: alloc creates the (empty) wrapper
|
|
101
|
+
* object, initialize fills it in. We wrap NULL first, then create the real app
|
|
102
|
+
* in #initialize and store it — so a failure in the middle still leaves a
|
|
103
|
+
* valid, freeable object.
|
|
104
|
+
*/
|
|
105
|
+
static VALUE app_alloc(VALUE klass) {
|
|
106
|
+
return TypedData_Wrap_Struct(klass, &app_data_type, NULL);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* App.new(width:, height:, caption:) — keyword arguments, matching the shape a
|
|
110
|
+
* subclass's own initialize will forward to via super. */
|
|
111
|
+
static VALUE app_initialize(int argc, VALUE *argv, VALUE self) {
|
|
112
|
+
VALUE opts = Qnil;
|
|
113
|
+
rb_scan_args(argc, argv, "0:", &opts); /* no positional args, keywords only */
|
|
114
|
+
if (NIL_P(opts)) {
|
|
115
|
+
rb_raise(rb_eArgError, "missing keywords: :width, :height, :caption");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const ID keys[4] = { id_width, id_height, id_caption, id_media_root };
|
|
119
|
+
VALUE values[4];
|
|
120
|
+
/* 3 required, 1 optional: raises on a missing or unknown keyword. An absent
|
|
121
|
+
* optional comes back as Qundef. */
|
|
122
|
+
rb_get_kwargs(opts, keys, 3, 1, values);
|
|
123
|
+
|
|
124
|
+
rgame_app *app = rgame_app_create(NUM2INT(values[0]), NUM2INT(values[1]),
|
|
125
|
+
StringValueCStr(values[2]));
|
|
126
|
+
if (!app) {
|
|
127
|
+
rb_raise(rb_eRuntimeError, "failed to create rgame app");
|
|
128
|
+
}
|
|
129
|
+
/* Store the pointer into the already-wrapped object. */
|
|
130
|
+
RTYPEDDATA_DATA(self) = app;
|
|
131
|
+
|
|
132
|
+
/* Where App#assets resolves relative paths from. It means nothing to the C
|
|
133
|
+
* engine — no file is read here — so it is kept as a plain Ruby ivar and
|
|
134
|
+
* lib/rgame/core/app.rb owns everything that reads it. It is a keyword here
|
|
135
|
+
* rather than a writer so that a game says it once, in the same call that
|
|
136
|
+
* makes the window, and cannot set it after an asset has already loaded. */
|
|
137
|
+
rb_iv_set(self, "@media_root",
|
|
138
|
+
values[3] == Qundef ? rb_str_new_cstr("media") : StringValue(values[3]));
|
|
139
|
+
return self;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ------------------------------------------------------------------------- *
|
|
143
|
+
* Callback trampolines
|
|
144
|
+
*
|
|
145
|
+
* rgame_app_run takes plain C function pointers and an opaque userdata. We
|
|
146
|
+
* pass a run_state living on app_run's C stack, and each trampoline calls the
|
|
147
|
+
* corresponding method on the App object.
|
|
148
|
+
*
|
|
149
|
+
* Every one of them has **fixed arity** — `update` takes one argument, `draw`
|
|
150
|
+
* takes none — and that is worth saying out loud because it is easy to give
|
|
151
|
+
* away. A generic wrapper written as `|*args|` allocates a throwaway Array on
|
|
152
|
+
* every call, and these are called several times per frame forever: an
|
|
153
|
+
* allocation floor no amount of careful game code can get under, and noise in
|
|
154
|
+
* any per-frame allocation reading. Hand-written bindings get this for free;
|
|
155
|
+
* generated ones often do not.
|
|
156
|
+
*
|
|
157
|
+
* Exception safety is the interesting part. A Ruby callback may raise, and a
|
|
158
|
+
* raise is a longjmp — it would tear straight through rgame_app_run's C frame,
|
|
159
|
+
* skipping the rest of the loop and leaving the SDL window up until the GC
|
|
160
|
+
* eventually collects the App. So every callback runs under rb_protect, which
|
|
161
|
+
* catches the unwind and hands back a non-zero tag instead. We stash the
|
|
162
|
+
* exception, ask the engine to close, let rgame_app_run return normally, and
|
|
163
|
+
* only then re-raise from #run — by which point the C loop has unwound cleanly
|
|
164
|
+
* on its own terms.
|
|
165
|
+
*
|
|
166
|
+
* Both VALUEs in run_state live on the C stack, which Ruby's GC scans
|
|
167
|
+
* conservatively, so the captured exception stays alive without a mark
|
|
168
|
+
* function.
|
|
169
|
+
* ------------------------------------------------------------------------- */
|
|
170
|
+
|
|
171
|
+
typedef struct {
|
|
172
|
+
VALUE self;
|
|
173
|
+
rgame_app *app;
|
|
174
|
+
int failed; /* set once a callback has unwound; suppresses further calls */
|
|
175
|
+
VALUE exception; /* what to re-raise once the loop has exited */
|
|
176
|
+
} run_state;
|
|
177
|
+
|
|
178
|
+
/* rb_protect and rb_rescue2 can only call a function taking one VALUE, so the
|
|
179
|
+
* call is packed into a struct and passed by pointer. `caught` is the way the
|
|
180
|
+
* rescue handler reports back. */
|
|
181
|
+
typedef struct {
|
|
182
|
+
VALUE self;
|
|
183
|
+
ID method;
|
|
184
|
+
int argc;
|
|
185
|
+
const VALUE *argv;
|
|
186
|
+
VALUE caught; /* the exception, if the callback raised one */
|
|
187
|
+
} funcall_args;
|
|
188
|
+
|
|
189
|
+
static VALUE do_funcall(VALUE arg) {
|
|
190
|
+
const funcall_args *a = (const funcall_args *)arg;
|
|
191
|
+
return rb_funcallv(a->self, a->method, a->argc, a->argv);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static VALUE capture_exception(VALUE arg, VALUE err) {
|
|
195
|
+
funcall_args *a = (funcall_args *)arg;
|
|
196
|
+
a->caught = err;
|
|
197
|
+
return Qnil;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/*
|
|
201
|
+
* Run the callback, turning any exception into a plain value handed back via
|
|
202
|
+
* `caught`. rb_rescue2 is used rather than reading rb_errinfo() after an
|
|
203
|
+
* rb_protect, because errinfo is only safe to inspect when the unwind really
|
|
204
|
+
* was an exception — see protected_call.
|
|
205
|
+
*/
|
|
206
|
+
static VALUE call_with_rescue(VALUE arg) {
|
|
207
|
+
return rb_rescue2(do_funcall, arg, capture_exception, arg, rb_eException, (VALUE)0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static VALUE protected_call(run_state *rs, ID method, int argc, const VALUE *argv) {
|
|
211
|
+
/* Already unwinding: run no further Ruby, just let the loop finish. */
|
|
212
|
+
if (rs->failed) {
|
|
213
|
+
return Qnil;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
funcall_args args = { rs->self, method, argc, argv, Qnil };
|
|
217
|
+
int state = 0;
|
|
218
|
+
VALUE result = rb_protect(call_with_rescue, (VALUE)&args, &state);
|
|
219
|
+
|
|
220
|
+
if (state) {
|
|
221
|
+
/*
|
|
222
|
+
* rb_rescue2 already handled every *exception*, so reaching here means
|
|
223
|
+
* a non-exception unwind: throw, break, next or return crossing the C
|
|
224
|
+
* frame. Those cannot be deferred and re-raised later — rb_protect
|
|
225
|
+
* pops the tag holding the jump's target as it returns, so replaying
|
|
226
|
+
* it afterwards jumps into freed state.
|
|
227
|
+
*
|
|
228
|
+
* Nor can the pending errinfo be inspected to find out what happened:
|
|
229
|
+
* CRuby stashes an internal throw-data object there, which is a
|
|
230
|
+
* T_IMEMO rather than a real object, so even asking for its class
|
|
231
|
+
* walks garbage. (Both of those were found the hard way, as
|
|
232
|
+
* segfaults.) So errinfo is cleared without ever being read, and the
|
|
233
|
+
* situation is reported as an ordinary error.
|
|
234
|
+
*/
|
|
235
|
+
rb_set_errinfo(Qnil);
|
|
236
|
+
rs->failed = 1;
|
|
237
|
+
rs->exception = rb_exc_new_cstr(
|
|
238
|
+
rb_eRuntimeError,
|
|
239
|
+
"non-local exit (throw/break/return) crossed the rgame main loop; "
|
|
240
|
+
"call #close to stop the loop instead");
|
|
241
|
+
rgame_app_close(rs->app);
|
|
242
|
+
return Qnil;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!NIL_P(args.caught)) {
|
|
246
|
+
rs->failed = 1;
|
|
247
|
+
rs->exception = args.caught;
|
|
248
|
+
rgame_app_close(rs->app);
|
|
249
|
+
return Qnil;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static void tramp_frame_begin(void *userdata) {
|
|
256
|
+
protected_call((run_state *)userdata, id_frame_begin, 0, NULL);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static void tramp_update(void *userdata, double dt_seconds) {
|
|
260
|
+
VALUE dt = DBL2NUM(dt_seconds);
|
|
261
|
+
protected_call((run_state *)userdata, id_update, 1, &dt);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static void tramp_draw(void *userdata) {
|
|
265
|
+
protected_call((run_state *)userdata, id_draw, 0, NULL);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static int tramp_needs_redraw(void *userdata) {
|
|
269
|
+
VALUE result = protected_call((run_state *)userdata, id_needs_redraw, 0, NULL);
|
|
270
|
+
return RTEST(result) ? 1 : 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static void tramp_button_down(void *userdata, int button_id) {
|
|
274
|
+
VALUE id = INT2NUM(button_id);
|
|
275
|
+
protected_call((run_state *)userdata, id_button_down, 1, &id);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static void tramp_button_up(void *userdata, int button_id) {
|
|
279
|
+
VALUE id = INT2NUM(button_id);
|
|
280
|
+
protected_call((run_state *)userdata, id_button_up, 1, &id);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
static void tramp_resize(void *userdata, int width, int height) {
|
|
284
|
+
VALUE args[2] = { INT2NUM(width), INT2NUM(height) };
|
|
285
|
+
protected_call((run_state *)userdata, id_resize, 2, args);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
static void tramp_gamepad_connected(void *userdata, int slot) {
|
|
289
|
+
VALUE arg = INT2NUM(slot);
|
|
290
|
+
protected_call((run_state *)userdata, id_gamepad_connected, 1, &arg);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static void tramp_gamepad_disconnected(void *userdata, int slot) {
|
|
294
|
+
VALUE arg = INT2NUM(slot);
|
|
295
|
+
protected_call((run_state *)userdata, id_gamepad_disconnected, 1, &arg);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* ------------------------------------------------------------------------- *
|
|
299
|
+
* app.run
|
|
300
|
+
* ------------------------------------------------------------------------- */
|
|
301
|
+
|
|
302
|
+
static VALUE app_run(VALUE self) {
|
|
303
|
+
rgame_app *app = rgame_app_unwrap(self);
|
|
304
|
+
run_state rs = { self, app, 0, Qnil };
|
|
305
|
+
|
|
306
|
+
rgame_app_callbacks callbacks = {
|
|
307
|
+
.frame_begin = tramp_frame_begin,
|
|
308
|
+
.update = tramp_update,
|
|
309
|
+
.needs_redraw = tramp_needs_redraw,
|
|
310
|
+
.draw = tramp_draw,
|
|
311
|
+
.button_down = tramp_button_down,
|
|
312
|
+
.button_up = tramp_button_up,
|
|
313
|
+
.resize = tramp_resize,
|
|
314
|
+
.gamepad_connected = tramp_gamepad_connected,
|
|
315
|
+
.gamepad_disconnected = tramp_gamepad_disconnected,
|
|
316
|
+
.userdata = &rs,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
rgame_app_run(app, &callbacks);
|
|
320
|
+
|
|
321
|
+
/* A callback unwound: the loop has now exited on its own terms, so it is
|
|
322
|
+
* safe to raise. Class, message and backtrace are the original ones. */
|
|
323
|
+
if (rs.failed) {
|
|
324
|
+
rb_exc_raise(rs.exception);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return self;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* ------------------------------------------------------------------------- *
|
|
331
|
+
* Window queries and control
|
|
332
|
+
* ------------------------------------------------------------------------- */
|
|
333
|
+
|
|
334
|
+
static VALUE app_close(VALUE self) {
|
|
335
|
+
rgame_app_close(rgame_app_unwrap(self));
|
|
336
|
+
return self;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static VALUE app_width(VALUE self) {
|
|
340
|
+
return INT2NUM(rgame_app_width(rgame_app_unwrap(self)));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static VALUE app_height(VALUE self) {
|
|
344
|
+
return INT2NUM(rgame_app_height(rgame_app_unwrap(self)));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
static VALUE app_caption(VALUE self) {
|
|
348
|
+
const char *title = rgame_app_title(rgame_app_unwrap(self));
|
|
349
|
+
return rb_utf8_str_new_cstr(title ? title : "");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
static VALUE app_set_caption(VALUE self, VALUE title) {
|
|
353
|
+
rgame_app_set_title(rgame_app_unwrap(self), StringValueCStr(title));
|
|
354
|
+
return title;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
* Raw input queries. These take the numeric device and button/axis ids from
|
|
359
|
+
* RGame::Core::Input rather than symbolic action names — turning `:fire` into
|
|
360
|
+
* an id is the binding table's job, and that lives in Ruby (see
|
|
361
|
+
* lib/rgame/core/input.rb) because it is configuration, not mechanism.
|
|
362
|
+
*/
|
|
363
|
+
static VALUE app_input_down_p(VALUE self, VALUE device, VALUE button_id) {
|
|
364
|
+
return rgame_app_input_down(rgame_app_unwrap(self), NUM2INT(device), NUM2INT(button_id))
|
|
365
|
+
? Qtrue
|
|
366
|
+
: Qfalse;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
static VALUE app_input_axis(VALUE self, VALUE device, VALUE axis_id) {
|
|
370
|
+
return DBL2NUM(rgame_app_input_axis(rgame_app_unwrap(self), NUM2INT(device), NUM2INT(axis_id)));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/*
|
|
374
|
+
* Gamepad readout. Named `gamepad_present?` rather than `gamepad_connected?`
|
|
375
|
+
* on purpose: `gamepad_connected` is already the hot-plug *callback* a subclass
|
|
376
|
+
* overrides, and two methods differing only by a trailing `?` is a trap. The
|
|
377
|
+
* friendly name lives on RGame::Core::Gamepad, which reads better anyway.
|
|
378
|
+
*/
|
|
379
|
+
static VALUE app_gamepad_present_p(VALUE self, VALUE slot) {
|
|
380
|
+
return rgame_app_gamepad_connected(rgame_app_unwrap(self), NUM2INT(slot)) ? Qtrue : Qfalse;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* nil for an empty slot, or for a pad SDL has no name for. */
|
|
384
|
+
static VALUE app_gamepad_name(VALUE self, VALUE slot) {
|
|
385
|
+
const char *name = rgame_app_gamepad_name(rgame_app_unwrap(self), NUM2INT(slot));
|
|
386
|
+
return name ? rb_utf8_str_new_cstr(name) : Qnil;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
static VALUE app_gamepad_count(VALUE self) {
|
|
390
|
+
return INT2NUM(rgame_app_gamepad_count(rgame_app_unwrap(self)));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
static VALUE app_ticks_ms(VALUE self) {
|
|
394
|
+
return UINT2NUM(rgame_app_ticks_ms(rgame_app_unwrap(self)));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
static VALUE app_fps(VALUE self) {
|
|
398
|
+
return DBL2NUM(rgame_app_fps(rgame_app_unwrap(self)));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* ------------------------------------------------------------------------- *
|
|
402
|
+
* Default callbacks
|
|
403
|
+
*
|
|
404
|
+
* Defined so a subclass only overrides the hooks it actually cares about, and
|
|
405
|
+
* so the trampolines can call unconditionally without checking respond_to?.
|
|
406
|
+
* ------------------------------------------------------------------------- */
|
|
407
|
+
|
|
408
|
+
static VALUE app_default_frame_begin(VALUE self) {
|
|
409
|
+
(void)self;
|
|
410
|
+
return Qnil;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
static VALUE app_default_update(VALUE self, VALUE dt_seconds) {
|
|
414
|
+
(void)self;
|
|
415
|
+
(void)dt_seconds;
|
|
416
|
+
return Qnil;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
static VALUE app_default_draw(VALUE self) {
|
|
420
|
+
(void)self;
|
|
421
|
+
return Qnil;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* Default is "always redraw", matching a NULL needs_redraw in the C API. */
|
|
425
|
+
static VALUE app_default_needs_redraw(VALUE self) {
|
|
426
|
+
(void)self;
|
|
427
|
+
return Qtrue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static VALUE app_default_button(VALUE self, VALUE button_id) {
|
|
431
|
+
(void)self;
|
|
432
|
+
(void)button_id;
|
|
433
|
+
return Qnil;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
static VALUE app_default_resize(VALUE self, VALUE width, VALUE height) {
|
|
437
|
+
(void)self;
|
|
438
|
+
(void)width;
|
|
439
|
+
(void)height;
|
|
440
|
+
return Qnil;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
static VALUE app_default_gamepad(VALUE self, VALUE slot) {
|
|
444
|
+
(void)self;
|
|
445
|
+
(void)slot;
|
|
446
|
+
return Qnil;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* ------------------------------------------------------------------------- *
|
|
450
|
+
* Entry point. Ruby calls Init_<basename of the required path> when the .so is
|
|
451
|
+
* loaded; we require it as "rgame/core_ext", so this must be
|
|
452
|
+
* Init_core_ext, matching create_makefile("rgame/core_ext") in
|
|
453
|
+
* extconf.rb.
|
|
454
|
+
* ------------------------------------------------------------------------- */
|
|
455
|
+
|
|
456
|
+
void Init_core_ext(void) {
|
|
457
|
+
id_frame_begin = rb_intern("frame_begin");
|
|
458
|
+
id_update = rb_intern("update");
|
|
459
|
+
id_draw = rb_intern("draw");
|
|
460
|
+
id_needs_redraw = rb_intern("needs_redraw?");
|
|
461
|
+
id_button_down = rb_intern("button_down");
|
|
462
|
+
id_button_up = rb_intern("button_up");
|
|
463
|
+
id_resize = rb_intern("resize");
|
|
464
|
+
id_gamepad_connected = rb_intern("gamepad_connected");
|
|
465
|
+
id_gamepad_disconnected = rb_intern("gamepad_disconnected");
|
|
466
|
+
id_width = rb_intern("width");
|
|
467
|
+
id_height = rb_intern("height");
|
|
468
|
+
id_caption = rb_intern("caption");
|
|
469
|
+
id_media_root = rb_intern("media_root");
|
|
470
|
+
|
|
471
|
+
/*
|
|
472
|
+
* rb_define_module is idempotent — it returns the existing RGame if some
|
|
473
|
+
* other extension or Ruby file defined it first, so load order between the
|
|
474
|
+
* two extensions doesn't matter.
|
|
475
|
+
*/
|
|
476
|
+
VALUE mRGame = rb_define_module("RGame");
|
|
477
|
+
VALUE mCore = rb_define_module_under(mRGame, "Core");
|
|
478
|
+
VALUE cApp = rb_define_class_under(mCore, "App", rb_cObject);
|
|
479
|
+
|
|
480
|
+
rb_define_alloc_func(cApp, app_alloc);
|
|
481
|
+
rb_define_method(cApp, "initialize", app_initialize, -1);
|
|
482
|
+
rb_define_method(cApp, "run", app_run, 0);
|
|
483
|
+
|
|
484
|
+
rb_define_method(cApp, "close", app_close, 0);
|
|
485
|
+
rb_define_method(cApp, "width", app_width, 0);
|
|
486
|
+
rb_define_method(cApp, "height", app_height, 0);
|
|
487
|
+
rb_define_method(cApp, "caption", app_caption, 0);
|
|
488
|
+
rb_define_method(cApp, "caption=", app_set_caption, 1);
|
|
489
|
+
rb_define_method(cApp, "ticks_ms", app_ticks_ms, 0);
|
|
490
|
+
rb_define_method(cApp, "fps", app_fps, 0);
|
|
491
|
+
rb_define_method(cApp, "input_down?", app_input_down_p, 2);
|
|
492
|
+
rb_define_method(cApp, "input_axis", app_input_axis, 2);
|
|
493
|
+
rb_define_method(cApp, "gamepad_present?", app_gamepad_present_p, 1);
|
|
494
|
+
rb_define_method(cApp, "gamepad_name", app_gamepad_name, 1);
|
|
495
|
+
rb_define_method(cApp, "gamepad_count", app_gamepad_count, 0);
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
rb_define_method(cApp, "frame_begin", app_default_frame_begin, 0);
|
|
499
|
+
rb_define_method(cApp, "update", app_default_update, 1);
|
|
500
|
+
rb_define_method(cApp, "draw", app_default_draw, 0);
|
|
501
|
+
rb_define_method(cApp, "needs_redraw?", app_default_needs_redraw, 0);
|
|
502
|
+
rb_define_method(cApp, "button_down", app_default_button, 1);
|
|
503
|
+
rb_define_method(cApp, "button_up", app_default_button, 1);
|
|
504
|
+
rb_define_method(cApp, "resize", app_default_resize, 2);
|
|
505
|
+
rb_define_method(cApp, "gamepad_connected", app_default_gamepad, 1);
|
|
506
|
+
rb_define_method(cApp, "gamepad_disconnected", app_default_gamepad, 1);
|
|
507
|
+
|
|
508
|
+
rgame_init_image(mCore);
|
|
509
|
+
rgame_init_renderer(mCore);
|
|
510
|
+
rgame_init_recording(mCore);
|
|
511
|
+
rgame_init_font(mCore);
|
|
512
|
+
rgame_init_audio(mCore);
|
|
513
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#ifndef RGAME_CORE_EXT_H
|
|
2
|
+
#define RGAME_CORE_EXT_H
|
|
3
|
+
|
|
4
|
+
#include <ruby.h>
|
|
5
|
+
|
|
6
|
+
#include "rgame/core.h"
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Each Ruby-visible class in this extension beyond App exposes one init
|
|
10
|
+
* function, and core_ext.c calls them all — the same shape as
|
|
11
|
+
* ext/rgame_util/util_ext.h. Adding a class means adding a file and one line
|
|
12
|
+
* there, rather than growing one file that owns everything.
|
|
13
|
+
*
|
|
14
|
+
* App itself still lives in core_ext.c: it is the entry point's own subject,
|
|
15
|
+
* and the callback trampolines that drive the main loop are inseparable from
|
|
16
|
+
* it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
void rgame_init_image(VALUE mCore);
|
|
20
|
+
void rgame_init_renderer(VALUE mCore);
|
|
21
|
+
void rgame_init_recording(VALUE mCore);
|
|
22
|
+
void rgame_init_font(VALUE mCore);
|
|
23
|
+
|
|
24
|
+
/* Defines Audio, Sample and Song together — see audio_ext.c for why those three
|
|
25
|
+
* share a file when everything else here does not. */
|
|
26
|
+
void rgame_init_audio(VALUE mCore);
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Wraps a baked recording in a Ruby object. Only Renderer#record calls this:
|
|
30
|
+
* `images` is the Array of Images drawn while recording, which the new object
|
|
31
|
+
* marks so their GPU textures outlive it (recording_ext.c says why).
|
|
32
|
+
*/
|
|
33
|
+
VALUE rgame_recording_wrap(rgame_recording *recording, VALUE app_object, rgame_app *app,
|
|
34
|
+
VALUE images);
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* The engine handle behind a Ruby RGame::Core::App. Raises TypeError if the
|
|
38
|
+
* object is not one — TypedData does that check itself, which is why anything
|
|
39
|
+
* needing an app takes the Ruby object and calls this rather than duck-typing.
|
|
40
|
+
*/
|
|
41
|
+
rgame_app *rgame_app_unwrap(VALUE app);
|
|
42
|
+
|
|
43
|
+
/* The engine handle behind a Ruby RGame::Core::Image, for the renderer to draw.
|
|
44
|
+
* Raises TypeError on anything that is not an Image. */
|
|
45
|
+
rgame_image *rgame_image_unwrap(VALUE image);
|
|
46
|
+
|
|
47
|
+
/* The engine handle behind a Ruby RGame::Core::Font, for the renderer to draw
|
|
48
|
+
* text with. Raises TypeError on anything that is not a Font. */
|
|
49
|
+
rgame_font *rgame_font_unwrap(VALUE font);
|
|
50
|
+
|
|
51
|
+
#endif /* RGAME_CORE_EXT_H */
|