rgame 0.1.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +228 -0
- data/README.md +142 -243
- data/docs/api/README.md +141 -90
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -166
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1045 -254
- data/docs/api/drawing.md +187 -119
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +165 -45
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +344 -85
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +503 -123
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +197 -64
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +400 -147
- data/docs/api/ui.md +927 -0
- data/docs/api/values.md +234 -19
- data/examples/assets/README.md +322 -0
- data/examples/assets/blip.ogg +0 -0
- data/examples/assets/glyphs.json +5 -0
- data/examples/assets/glyphs.png +0 -0
- data/examples/assets/hero.json +12 -0
- data/examples/assets/hero.png +0 -0
- data/examples/assets/icons.json +13 -0
- data/examples/assets/icons.png +0 -0
- data/examples/assets/music.ogg +0 -0
- data/examples/assets/skills.json +10 -0
- data/examples/assets/skills.png +0 -0
- data/examples/assets/tileset.png +0 -0
- data/examples/assets/tileset.tsx +65 -0
- data/examples/assets/town.tmx +26 -0
- data/examples/assets/ui.json +11 -0
- data/examples/assets/ui.png +0 -0
- data/examples/collision/locales/en.yml +8 -0
- data/examples/collision/main.rb +316 -0
- data/examples/collision_tiles/locales/en.yml +9 -0
- data/examples/collision_tiles/main.rb +274 -0
- data/examples/fullscreen/locales/en.yml +10 -0
- data/examples/fullscreen/main.rb +216 -0
- data/examples/game_menu/locales/en.yml +8 -0
- data/examples/game_menu/main.rb +170 -0
- data/examples/input_glyphs/locales/en.yml +14 -0
- data/examples/input_glyphs/main.rb +213 -0
- data/examples/jump_topdown/locales/en.yml +9 -0
- data/examples/jump_topdown/main.rb +178 -0
- data/examples/localization/locales/de.yml +12 -0
- data/examples/localization/locales/en.yml +13 -0
- data/examples/localization/main.rb +158 -0
- data/examples/menu_navigation/locales/en.yml +23 -0
- data/examples/menu_navigation/main.rb +365 -0
- data/examples/music/locales/en.yml +7 -0
- data/examples/music/main.rb +134 -0
- data/examples/pathfinding/locales/en.yml +17 -0
- data/examples/pathfinding/main.rb +298 -0
- data/examples/pooling/locales/en.yml +7 -0
- data/examples/pooling/main.rb +259 -0
- data/examples/quick_wheel/locales/en.yml +16 -0
- data/examples/quick_wheel/main.rb +184 -0
- data/examples/radial_menu/locales/en.yml +16 -0
- data/examples/radial_menu/main.rb +184 -0
- data/examples/save_load/locales/en.yml +11 -0
- data/examples/save_load/main.rb +207 -0
- data/examples/save_load_ids/locales/en.yml +11 -0
- data/examples/save_load_ids/main.rb +322 -0
- data/examples/scroll_map/locales/en.yml +4 -0
- data/examples/scroll_map/main.rb +140 -0
- data/examples/signals/locales/en.yml +6 -0
- data/examples/signals/main.rb +278 -0
- data/examples/skill_bar/locales/en.yml +14 -0
- data/examples/skill_bar/main.rb +159 -0
- data/examples/sound/locales/en.yml +6 -0
- data/examples/sound/main.rb +122 -0
- data/examples/split_screen/locales/en.yml +9 -0
- data/examples/split_screen/main.rb +304 -0
- data/examples/sprite/locales/en.yml +8 -0
- data/examples/sprite/main.rb +180 -0
- data/examples/timer/locales/en.yml +12 -0
- data/examples/timer/main.rb +273 -0
- data/examples/velocity/locales/en.yml +6 -0
- data/examples/velocity/main.rb +196 -0
- data/examples/walk/locales/en.yml +4 -0
- data/examples/walk/main.rb +99 -0
- data/exe/rgame +9 -0
- data/ext/README.md +6 -5
- data/ext/rgame_core/app/app.c +215 -11
- data/ext/rgame_core/app/locale.c +67 -0
- data/ext/rgame_core/app/locale.h +28 -0
- data/ext/rgame_core/audio/audio.c +113 -2
- data/ext/rgame_core/example.rb +18 -56
- data/ext/rgame_core/extconf.rb +16 -113
- data/ext/rgame_core/graphics/canvas.c +45 -4
- data/ext/rgame_core/graphics/canvas.h +65 -10
- data/ext/rgame_core/graphics/clip.c +22 -13
- data/ext/rgame_core/include/rgame/core.h +151 -5
- data/ext/rgame_core/input/gamepad.c +57 -3
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +46 -7
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/locale_ext.c +44 -0
- data/ext/rgame_core/ruby/recording_ext.c +1 -1
- data/ext/rgame_core/ruby/renderer_ext.c +42 -19
- data/ext/rgame_util/color_ext.c +12 -3
- data/ext/rgame_util/extconf.rb +2 -20
- data/ext/rgame_util/route_search.c +305 -0
- data/ext/rgame_util/route_search.h +86 -0
- data/ext/rgame_util/route_search_ext.c +150 -0
- data/ext/rgame_util/solid_grid.c +58 -0
- data/ext/rgame_util/solid_grid.h +49 -0
- data/ext/rgame_util/solid_grid_ext.c +161 -0
- data/ext/rgame_util/tile_sweep.c +164 -0
- data/ext/rgame_util/tile_sweep.h +62 -0
- data/ext/rgame_util/tile_sweep_ext.c +155 -0
- data/ext/rgame_util/util_ext.c +3 -0
- data/ext/rgame_util/util_ext.h +15 -0
- data/lib/rgame/boot.rb +0 -10
- data/lib/rgame/cli/new_project.rb +139 -0
- data/lib/rgame/cli/templates/Gemfile.tt +23 -0
- data/lib/rgame/cli/templates/README.md.tt +93 -0
- data/lib/rgame/cli/templates/Rakefile.tt +9 -0
- data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
- data/lib/rgame/cli/templates/game.rb.tt +23 -0
- data/lib/rgame/cli/templates/gitignore.tt +12 -0
- data/lib/rgame/cli/templates/main.rb.tt +11 -0
- data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
- data/lib/rgame/cli/templates/rspec.tt +2 -0
- data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
- data/lib/rgame/cli/templates/ruby-version.tt +1 -0
- data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
- data/lib/rgame/cli.rb +66 -0
- data/lib/rgame/core/app.rb +6 -42
- data/lib/rgame/core/asset_manager.rb +13 -31
- data/lib/rgame/core/audio.rb +37 -16
- data/lib/rgame/core/font.rb +0 -3
- data/lib/rgame/core/input.rb +35 -41
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/recording.rb +3 -1
- data/lib/rgame/core/renderer.rb +75 -84
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +77 -65
- data/lib/rgame/core/ui_atlas.rb +28 -13
- data/lib/rgame/core.rb +1 -8
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +1 -0
- data/lib/rgame/engine/audio_director.rb +36 -6
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +55 -10
- data/lib/rgame/engine/circle_collider.rb +4 -2
- data/lib/rgame/engine/collision_box.rb +26 -1
- data/lib/rgame/engine/collision_system.rb +110 -22
- data/lib/rgame/engine/component.rb +35 -1
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +31 -23
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +45 -0
- data/lib/rgame/engine/components/character_body.rb +21 -41
- data/lib/rgame/engine/components/circle_collider.rb +47 -11
- data/lib/rgame/engine/components/collision_world.rb +159 -31
- data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
- data/lib/rgame/engine/components/feet_collider.rb +61 -0
- data/lib/rgame/engine/components/hop.rb +76 -0
- data/lib/rgame/engine/components/identity.rb +73 -0
- data/lib/rgame/engine/components/mover.rb +285 -0
- data/lib/rgame/engine/components/navigator.rb +145 -0
- data/lib/rgame/engine/components/path_follow.rb +123 -31
- data/lib/rgame/engine/components/player_controller.rb +5 -2
- data/lib/rgame/engine/components/pool.rb +1 -1
- data/lib/rgame/engine/components/screen_wrap.rb +33 -11
- data/lib/rgame/engine/components/sprite.rb +22 -6
- data/lib/rgame/engine/components/targeting.rb +9 -11
- data/lib/rgame/engine/components/thrust_controller.rb +1 -1
- data/lib/rgame/engine/components/tile_world.rb +70 -29
- data/lib/rgame/engine/components/timer.rb +1 -1
- data/lib/rgame/engine/components/velocity.rb +23 -7
- data/lib/rgame/engine/components/wander_controller.rb +6 -2
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +45 -0
- data/lib/rgame/engine/debug_overlay.rb +23 -18
- data/lib/rgame/engine/i18n/plural.rb +45 -0
- data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
- data/lib/rgame/engine/i18n/template.rb +59 -0
- data/lib/rgame/engine/i18n.rb +276 -51
- data/lib/rgame/engine/input/action_mapper.rb +76 -22
- data/lib/rgame/engine/input/actions.rb +63 -12
- data/lib/rgame/engine/input/input_map.rb +196 -0
- data/lib/rgame/engine/layout.rb +82 -0
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +364 -80
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/player.rb +69 -0
- data/lib/rgame/engine/player_layer.rb +70 -0
- data/lib/rgame/engine/players.rb +205 -0
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +29 -7
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +53 -8
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +2 -3
- data/lib/rgame/engine/tile_map_layer.rb +82 -0
- data/lib/rgame/engine/tileset.rb +2 -4
- data/lib/rgame/engine/timer.rb +2 -2
- data/lib/rgame/engine/ui/button.rb +248 -0
- data/lib/rgame/engine/ui/column.rb +20 -0
- data/lib/rgame/engine/ui/icon_button.rb +93 -0
- data/lib/rgame/engine/ui/menu.rb +290 -0
- data/lib/rgame/engine/ui/navigation.rb +57 -0
- data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
- data/lib/rgame/engine/ui/option_button.rb +163 -0
- data/lib/rgame/engine/ui/panel_button.rb +32 -0
- data/lib/rgame/engine/ui/panel_menu.rb +36 -0
- data/lib/rgame/engine/ui/pointing.rb +146 -0
- data/lib/rgame/engine/ui/radial_menu.rb +85 -0
- data/lib/rgame/engine/ui/ring.rb +55 -0
- data/lib/rgame/engine/ui/row.rb +21 -0
- data/lib/rgame/engine/ui/shape_style.rb +102 -0
- data/lib/rgame/engine/ui/stack.rb +58 -0
- data/lib/rgame/engine/ui/stepping.rb +93 -0
- data/lib/rgame/engine/ui/text_button.rb +59 -0
- data/lib/rgame/engine/view.rb +76 -0
- data/lib/rgame/engine/viewports.rb +171 -0
- data/lib/rgame/engine/world_view.rb +71 -0
- data/lib/rgame/engine.rb +43 -25
- data/lib/rgame/game.rb +164 -22
- data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
- data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
- data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
- data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
- data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
- data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
- data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
- data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
- data/lib/rgame/rubocop/default.yml +39 -0
- data/lib/rgame/rubocop/plugin.rb +45 -0
- data/lib/rgame/rubocop.rb +11 -0
- data/lib/rgame/util/color.rb +20 -24
- data/lib/rgame/util/controls.rb +106 -44
- data/lib/rgame/util/route_search.rb +27 -0
- data/lib/rgame/util/save_file.rb +107 -0
- data/lib/rgame/util/solid_grid.rb +37 -0
- data/lib/rgame/util/tensor.rb +0 -9
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +123 -0
- data/lib/rgame/util.rb +5 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +176 -20
- data/lib/rgame/engine/actor.rb +0 -53
- data/lib/rgame/engine/body.rb +0 -49
- data/lib/rgame/engine/cached_label.rb +0 -33
- data/lib/rgame/engine/camera_view.rb +0 -28
- data/lib/rgame/engine/input/player_controller.rb +0 -14
- data/lib/rgame/engine/matrix.rb +0 -32
- data/lib/rgame/engine/resettable.rb +0 -67
- data/lib/rgame/engine/tile_collision.rb +0 -78
data/ext/rgame_core/app/app.c
CHANGED
|
@@ -95,7 +95,11 @@ struct rgame_app {
|
|
|
95
95
|
int drawing;
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
/* Desktop fullscreen, never a mode change — see the header for why. Named once
|
|
99
|
+
* so the creation flags and the runtime toggle cannot disagree. */
|
|
100
|
+
#define RGAME_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN_DESKTOP
|
|
101
|
+
|
|
102
|
+
rgame_app *rgame_app_create(int width, int height, const char *title, int fullscreen) {
|
|
99
103
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
|
|
100
104
|
SDL_Log("SDL_Init failed: %s", SDL_GetError());
|
|
101
105
|
return NULL;
|
|
@@ -114,7 +118,8 @@ rgame_app *rgame_app_create(int width, int height, const char *title) {
|
|
|
114
118
|
title,
|
|
115
119
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
116
120
|
width, height,
|
|
117
|
-
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
|
|
121
|
+
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
|
|
122
|
+
(fullscreen ? RGAME_FULLSCREEN_FLAG : 0u));
|
|
118
123
|
if (!app->window) {
|
|
119
124
|
SDL_Log("SDL_CreateWindow failed: %s", SDL_GetError());
|
|
120
125
|
free(app);
|
|
@@ -227,14 +232,168 @@ void rgame_app_gl_restore(const rgame_gl_context_save *saved) {
|
|
|
227
232
|
* the compiler checks — it must not include SDL. These assertions close that
|
|
228
233
|
* gap at compile time, so the two can never drift apart silently.
|
|
229
234
|
*/
|
|
230
|
-
_Static_assert(
|
|
231
|
-
|
|
232
|
-
_Static_assert(
|
|
233
|
-
|
|
234
|
-
_Static_assert(
|
|
235
|
-
|
|
236
|
-
_Static_assert(
|
|
237
|
-
|
|
235
|
+
_Static_assert(RGAME_KEY_A == SDL_SCANCODE_A,
|
|
236
|
+
"key id must match SDL scancode");
|
|
237
|
+
_Static_assert(RGAME_KEY_B == SDL_SCANCODE_B,
|
|
238
|
+
"key id must match SDL scancode");
|
|
239
|
+
_Static_assert(RGAME_KEY_C == SDL_SCANCODE_C,
|
|
240
|
+
"key id must match SDL scancode");
|
|
241
|
+
_Static_assert(RGAME_KEY_D == SDL_SCANCODE_D,
|
|
242
|
+
"key id must match SDL scancode");
|
|
243
|
+
_Static_assert(RGAME_KEY_E == SDL_SCANCODE_E,
|
|
244
|
+
"key id must match SDL scancode");
|
|
245
|
+
_Static_assert(RGAME_KEY_F == SDL_SCANCODE_F,
|
|
246
|
+
"key id must match SDL scancode");
|
|
247
|
+
_Static_assert(RGAME_KEY_G == SDL_SCANCODE_G,
|
|
248
|
+
"key id must match SDL scancode");
|
|
249
|
+
_Static_assert(RGAME_KEY_H == SDL_SCANCODE_H,
|
|
250
|
+
"key id must match SDL scancode");
|
|
251
|
+
_Static_assert(RGAME_KEY_I == SDL_SCANCODE_I,
|
|
252
|
+
"key id must match SDL scancode");
|
|
253
|
+
_Static_assert(RGAME_KEY_J == SDL_SCANCODE_J,
|
|
254
|
+
"key id must match SDL scancode");
|
|
255
|
+
_Static_assert(RGAME_KEY_K == SDL_SCANCODE_K,
|
|
256
|
+
"key id must match SDL scancode");
|
|
257
|
+
_Static_assert(RGAME_KEY_L == SDL_SCANCODE_L,
|
|
258
|
+
"key id must match SDL scancode");
|
|
259
|
+
_Static_assert(RGAME_KEY_M == SDL_SCANCODE_M,
|
|
260
|
+
"key id must match SDL scancode");
|
|
261
|
+
_Static_assert(RGAME_KEY_N == SDL_SCANCODE_N,
|
|
262
|
+
"key id must match SDL scancode");
|
|
263
|
+
_Static_assert(RGAME_KEY_O == SDL_SCANCODE_O,
|
|
264
|
+
"key id must match SDL scancode");
|
|
265
|
+
_Static_assert(RGAME_KEY_P == SDL_SCANCODE_P,
|
|
266
|
+
"key id must match SDL scancode");
|
|
267
|
+
_Static_assert(RGAME_KEY_Q == SDL_SCANCODE_Q,
|
|
268
|
+
"key id must match SDL scancode");
|
|
269
|
+
_Static_assert(RGAME_KEY_R == SDL_SCANCODE_R,
|
|
270
|
+
"key id must match SDL scancode");
|
|
271
|
+
_Static_assert(RGAME_KEY_S == SDL_SCANCODE_S,
|
|
272
|
+
"key id must match SDL scancode");
|
|
273
|
+
_Static_assert(RGAME_KEY_T == SDL_SCANCODE_T,
|
|
274
|
+
"key id must match SDL scancode");
|
|
275
|
+
_Static_assert(RGAME_KEY_U == SDL_SCANCODE_U,
|
|
276
|
+
"key id must match SDL scancode");
|
|
277
|
+
_Static_assert(RGAME_KEY_V == SDL_SCANCODE_V,
|
|
278
|
+
"key id must match SDL scancode");
|
|
279
|
+
_Static_assert(RGAME_KEY_W == SDL_SCANCODE_W,
|
|
280
|
+
"key id must match SDL scancode");
|
|
281
|
+
_Static_assert(RGAME_KEY_X == SDL_SCANCODE_X,
|
|
282
|
+
"key id must match SDL scancode");
|
|
283
|
+
_Static_assert(RGAME_KEY_Y == SDL_SCANCODE_Y,
|
|
284
|
+
"key id must match SDL scancode");
|
|
285
|
+
_Static_assert(RGAME_KEY_Z == SDL_SCANCODE_Z,
|
|
286
|
+
"key id must match SDL scancode");
|
|
287
|
+
_Static_assert(RGAME_KEY_1 == SDL_SCANCODE_1,
|
|
288
|
+
"key id must match SDL scancode");
|
|
289
|
+
_Static_assert(RGAME_KEY_2 == SDL_SCANCODE_2,
|
|
290
|
+
"key id must match SDL scancode");
|
|
291
|
+
_Static_assert(RGAME_KEY_3 == SDL_SCANCODE_3,
|
|
292
|
+
"key id must match SDL scancode");
|
|
293
|
+
_Static_assert(RGAME_KEY_4 == SDL_SCANCODE_4,
|
|
294
|
+
"key id must match SDL scancode");
|
|
295
|
+
_Static_assert(RGAME_KEY_5 == SDL_SCANCODE_5,
|
|
296
|
+
"key id must match SDL scancode");
|
|
297
|
+
_Static_assert(RGAME_KEY_6 == SDL_SCANCODE_6,
|
|
298
|
+
"key id must match SDL scancode");
|
|
299
|
+
_Static_assert(RGAME_KEY_7 == SDL_SCANCODE_7,
|
|
300
|
+
"key id must match SDL scancode");
|
|
301
|
+
_Static_assert(RGAME_KEY_8 == SDL_SCANCODE_8,
|
|
302
|
+
"key id must match SDL scancode");
|
|
303
|
+
_Static_assert(RGAME_KEY_9 == SDL_SCANCODE_9,
|
|
304
|
+
"key id must match SDL scancode");
|
|
305
|
+
_Static_assert(RGAME_KEY_0 == SDL_SCANCODE_0,
|
|
306
|
+
"key id must match SDL scancode");
|
|
307
|
+
_Static_assert(RGAME_KEY_RETURN == SDL_SCANCODE_RETURN,
|
|
308
|
+
"key id must match SDL scancode");
|
|
309
|
+
_Static_assert(RGAME_KEY_ESCAPE == SDL_SCANCODE_ESCAPE,
|
|
310
|
+
"key id must match SDL scancode");
|
|
311
|
+
_Static_assert(RGAME_KEY_BACKSPACE == SDL_SCANCODE_BACKSPACE,
|
|
312
|
+
"key id must match SDL scancode");
|
|
313
|
+
_Static_assert(RGAME_KEY_TAB == SDL_SCANCODE_TAB,
|
|
314
|
+
"key id must match SDL scancode");
|
|
315
|
+
_Static_assert(RGAME_KEY_SPACE == SDL_SCANCODE_SPACE,
|
|
316
|
+
"key id must match SDL scancode");
|
|
317
|
+
_Static_assert(RGAME_KEY_MINUS == SDL_SCANCODE_MINUS,
|
|
318
|
+
"key id must match SDL scancode");
|
|
319
|
+
_Static_assert(RGAME_KEY_EQUALS == SDL_SCANCODE_EQUALS,
|
|
320
|
+
"key id must match SDL scancode");
|
|
321
|
+
_Static_assert(RGAME_KEY_LEFTBRACKET == SDL_SCANCODE_LEFTBRACKET,
|
|
322
|
+
"key id must match SDL scancode");
|
|
323
|
+
_Static_assert(RGAME_KEY_RIGHTBRACKET == SDL_SCANCODE_RIGHTBRACKET,
|
|
324
|
+
"key id must match SDL scancode");
|
|
325
|
+
_Static_assert(RGAME_KEY_BACKSLASH == SDL_SCANCODE_BACKSLASH,
|
|
326
|
+
"key id must match SDL scancode");
|
|
327
|
+
_Static_assert(RGAME_KEY_SEMICOLON == SDL_SCANCODE_SEMICOLON,
|
|
328
|
+
"key id must match SDL scancode");
|
|
329
|
+
_Static_assert(RGAME_KEY_APOSTROPHE == SDL_SCANCODE_APOSTROPHE,
|
|
330
|
+
"key id must match SDL scancode");
|
|
331
|
+
_Static_assert(RGAME_KEY_GRAVE == SDL_SCANCODE_GRAVE,
|
|
332
|
+
"key id must match SDL scancode");
|
|
333
|
+
_Static_assert(RGAME_KEY_COMMA == SDL_SCANCODE_COMMA,
|
|
334
|
+
"key id must match SDL scancode");
|
|
335
|
+
_Static_assert(RGAME_KEY_PERIOD == SDL_SCANCODE_PERIOD,
|
|
336
|
+
"key id must match SDL scancode");
|
|
337
|
+
_Static_assert(RGAME_KEY_SLASH == SDL_SCANCODE_SLASH,
|
|
338
|
+
"key id must match SDL scancode");
|
|
339
|
+
_Static_assert(RGAME_KEY_CAPSLOCK == SDL_SCANCODE_CAPSLOCK,
|
|
340
|
+
"key id must match SDL scancode");
|
|
341
|
+
_Static_assert(RGAME_KEY_F1 == SDL_SCANCODE_F1,
|
|
342
|
+
"key id must match SDL scancode");
|
|
343
|
+
_Static_assert(RGAME_KEY_F2 == SDL_SCANCODE_F2,
|
|
344
|
+
"key id must match SDL scancode");
|
|
345
|
+
_Static_assert(RGAME_KEY_F3 == SDL_SCANCODE_F3,
|
|
346
|
+
"key id must match SDL scancode");
|
|
347
|
+
_Static_assert(RGAME_KEY_F4 == SDL_SCANCODE_F4,
|
|
348
|
+
"key id must match SDL scancode");
|
|
349
|
+
_Static_assert(RGAME_KEY_F5 == SDL_SCANCODE_F5,
|
|
350
|
+
"key id must match SDL scancode");
|
|
351
|
+
_Static_assert(RGAME_KEY_F6 == SDL_SCANCODE_F6,
|
|
352
|
+
"key id must match SDL scancode");
|
|
353
|
+
_Static_assert(RGAME_KEY_F7 == SDL_SCANCODE_F7,
|
|
354
|
+
"key id must match SDL scancode");
|
|
355
|
+
_Static_assert(RGAME_KEY_F8 == SDL_SCANCODE_F8,
|
|
356
|
+
"key id must match SDL scancode");
|
|
357
|
+
_Static_assert(RGAME_KEY_F9 == SDL_SCANCODE_F9,
|
|
358
|
+
"key id must match SDL scancode");
|
|
359
|
+
_Static_assert(RGAME_KEY_F10 == SDL_SCANCODE_F10,
|
|
360
|
+
"key id must match SDL scancode");
|
|
361
|
+
_Static_assert(RGAME_KEY_F11 == SDL_SCANCODE_F11,
|
|
362
|
+
"key id must match SDL scancode");
|
|
363
|
+
_Static_assert(RGAME_KEY_F12 == SDL_SCANCODE_F12,
|
|
364
|
+
"key id must match SDL scancode");
|
|
365
|
+
_Static_assert(RGAME_KEY_INSERT == SDL_SCANCODE_INSERT,
|
|
366
|
+
"key id must match SDL scancode");
|
|
367
|
+
_Static_assert(RGAME_KEY_HOME == SDL_SCANCODE_HOME,
|
|
368
|
+
"key id must match SDL scancode");
|
|
369
|
+
_Static_assert(RGAME_KEY_PAGEUP == SDL_SCANCODE_PAGEUP,
|
|
370
|
+
"key id must match SDL scancode");
|
|
371
|
+
_Static_assert(RGAME_KEY_DELETE == SDL_SCANCODE_DELETE,
|
|
372
|
+
"key id must match SDL scancode");
|
|
373
|
+
_Static_assert(RGAME_KEY_END == SDL_SCANCODE_END,
|
|
374
|
+
"key id must match SDL scancode");
|
|
375
|
+
_Static_assert(RGAME_KEY_PAGEDOWN == SDL_SCANCODE_PAGEDOWN,
|
|
376
|
+
"key id must match SDL scancode");
|
|
377
|
+
_Static_assert(RGAME_KEY_RIGHT == SDL_SCANCODE_RIGHT,
|
|
378
|
+
"key id must match SDL scancode");
|
|
379
|
+
_Static_assert(RGAME_KEY_LEFT == SDL_SCANCODE_LEFT,
|
|
380
|
+
"key id must match SDL scancode");
|
|
381
|
+
_Static_assert(RGAME_KEY_DOWN == SDL_SCANCODE_DOWN,
|
|
382
|
+
"key id must match SDL scancode");
|
|
383
|
+
_Static_assert(RGAME_KEY_UP == SDL_SCANCODE_UP,
|
|
384
|
+
"key id must match SDL scancode");
|
|
385
|
+
_Static_assert(RGAME_KEY_LCTRL == SDL_SCANCODE_LCTRL,
|
|
386
|
+
"key id must match SDL scancode");
|
|
387
|
+
_Static_assert(RGAME_KEY_LSHIFT == SDL_SCANCODE_LSHIFT,
|
|
388
|
+
"key id must match SDL scancode");
|
|
389
|
+
_Static_assert(RGAME_KEY_LALT == SDL_SCANCODE_LALT,
|
|
390
|
+
"key id must match SDL scancode");
|
|
391
|
+
_Static_assert(RGAME_KEY_RCTRL == SDL_SCANCODE_RCTRL,
|
|
392
|
+
"key id must match SDL scancode");
|
|
393
|
+
_Static_assert(RGAME_KEY_RSHIFT == SDL_SCANCODE_RSHIFT,
|
|
394
|
+
"key id must match SDL scancode");
|
|
395
|
+
_Static_assert(RGAME_KEY_RALT == SDL_SCANCODE_RALT,
|
|
396
|
+
"key id must match SDL scancode");
|
|
238
397
|
|
|
239
398
|
/* The snapshot array must be exactly as long as SDL's keyboard state array,
|
|
240
399
|
* since filling it is a straight copy. */
|
|
@@ -313,7 +472,13 @@ static void rgame_app_poll_events(rgame_app *app, const rgame_app_callbacks *cb)
|
|
|
313
472
|
break;
|
|
314
473
|
}
|
|
315
474
|
case SDL_WINDOWEVENT:
|
|
316
|
-
|
|
475
|
+
/* SIZE_CHANGED rather than RESIZED, and the difference matters:
|
|
476
|
+
* SDL sends RESIZED only when something *outside* the program
|
|
477
|
+
* resized the window, and SIZE_CHANGED for that case as well as for
|
|
478
|
+
* a size this program asked for. Toggling fullscreen is the second
|
|
479
|
+
* kind, so a listener on RESIZED alone never hears about it and the
|
|
480
|
+
* game carries on laying out for the old size. */
|
|
481
|
+
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
|
317
482
|
/* The viewport is set from the current size every frame, in the
|
|
318
483
|
* backend's begin_frame, so there is nothing to update here. */
|
|
319
484
|
if (cb->resize) {
|
|
@@ -400,6 +565,9 @@ void rgame_app_run(rgame_app *app, const rgame_app_callbacks *cb) {
|
|
|
400
565
|
|
|
401
566
|
rgame_draw_backend backend = rgame_gl_backend_table(&app->gl);
|
|
402
567
|
rgame_canvas_submit(&app->canvas, &backend);
|
|
568
|
+
if (cb->frame_end) {
|
|
569
|
+
cb->frame_end(cb->userdata);
|
|
570
|
+
}
|
|
403
571
|
SDL_GL_SwapWindow(app->window);
|
|
404
572
|
}
|
|
405
573
|
}
|
|
@@ -599,6 +767,23 @@ int rgame_app_push_clip(rgame_app *app, int x, int y, int width, int height) {
|
|
|
599
767
|
return 1;
|
|
600
768
|
}
|
|
601
769
|
|
|
770
|
+
void rgame_app_push_layer(rgame_app *app, double base) {
|
|
771
|
+
rgame_canvas *canvas = drawing_canvas(app);
|
|
772
|
+
if (canvas) {
|
|
773
|
+
rgame_canvas_push_layer(canvas, base);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
double rgame_app_layer(rgame_app *app) {
|
|
778
|
+
rgame_canvas *canvas = drawing_canvas(app);
|
|
779
|
+
return canvas ? rgame_canvas_layer(canvas) : 0.0;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
unsigned int rgame_app_next_layer_slot(rgame_app *app, int band) {
|
|
783
|
+
rgame_canvas *canvas = drawing_canvas(app);
|
|
784
|
+
return canvas ? rgame_canvas_next_slot(canvas, band) : 0;
|
|
785
|
+
}
|
|
786
|
+
|
|
602
787
|
void rgame_app_pop(rgame_app *app) {
|
|
603
788
|
rgame_canvas *canvas = drawing_canvas(app);
|
|
604
789
|
if (canvas) {
|
|
@@ -691,6 +876,25 @@ void rgame_app_set_title(rgame_app *app, const char *title) {
|
|
|
691
876
|
SDL_SetWindowTitle(app->window, title);
|
|
692
877
|
}
|
|
693
878
|
|
|
879
|
+
void rgame_app_set_fullscreen(rgame_app *app, int fullscreen) {
|
|
880
|
+
if (!app) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
SDL_SetWindowFullscreen(app->window, fullscreen ? RGAME_FULLSCREEN_FLAG : 0u);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
int rgame_app_fullscreen(const rgame_app *app) {
|
|
887
|
+
if (!app) {
|
|
888
|
+
return 0;
|
|
889
|
+
}
|
|
890
|
+
/* Tested against the whole mask rather than for any bit in it:
|
|
891
|
+
* SDL_WINDOW_FULLSCREEN_DESKTOP is SDL_WINDOW_FULLSCREEN plus one more,
|
|
892
|
+
* so `flags & FLAG` is also true of an exclusive-fullscreen window — which
|
|
893
|
+
* this engine never asks for, but a caller could have set behind our back. */
|
|
894
|
+
Uint32 flags = SDL_GetWindowFlags(app->window);
|
|
895
|
+
return (flags & RGAME_FULLSCREEN_FLAG) == RGAME_FULLSCREEN_FLAG;
|
|
896
|
+
}
|
|
897
|
+
|
|
694
898
|
int rgame_app_input_down(const rgame_app *app, int device, int button_id) {
|
|
695
899
|
return rgame_input_state_down(&app->input, device, button_id);
|
|
696
900
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* locale.c — the user's preferred locales, as the operating system reports
|
|
3
|
+
* them through SDL.
|
|
4
|
+
*
|
|
5
|
+
* rgame_preferred_locales is a thin shim over SDL_GetPreferredLocales; the
|
|
6
|
+
* joining and the buffer contract are rgame_locale_append, which is pure and
|
|
7
|
+
* Check-tested. Choosing among the locales is not here at all: it is
|
|
8
|
+
* RGame::Engine::I18n.choose, specced headless.
|
|
9
|
+
*
|
|
10
|
+
* SDL answers without SDL_Init (measured on 2.0.20 under Linux, where it reads
|
|
11
|
+
* LANG and then LANGUAGE), so none of this belongs to an app.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "app/locale.h"
|
|
15
|
+
|
|
16
|
+
#include <SDL2/SDL.h>
|
|
17
|
+
#include <string.h>
|
|
18
|
+
|
|
19
|
+
#include "rgame/core.h"
|
|
20
|
+
|
|
21
|
+
static size_t append_text(char *out, size_t capacity, size_t length, const char *text) {
|
|
22
|
+
for (; *text; text++, length++) {
|
|
23
|
+
if (length + 1 < capacity) {
|
|
24
|
+
out[length] = *text;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
size_t rgame_locale_append(char *out, size_t capacity, size_t length, const char *language,
|
|
31
|
+
const char *country) {
|
|
32
|
+
if (language == NULL || language[0] == '\0') {
|
|
33
|
+
return length;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (length > 0) {
|
|
37
|
+
length = append_text(out, capacity, length, ",");
|
|
38
|
+
}
|
|
39
|
+
length = append_text(out, capacity, length, language);
|
|
40
|
+
if (country != NULL && country[0] != '\0') {
|
|
41
|
+
length = append_text(out, capacity, length, "-");
|
|
42
|
+
length = append_text(out, capacity, length, country);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (capacity > 0) {
|
|
46
|
+
out[length < capacity ? length : capacity - 1] = '\0';
|
|
47
|
+
}
|
|
48
|
+
return length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
size_t rgame_preferred_locales(char *out, size_t capacity) {
|
|
52
|
+
if (capacity > 0) {
|
|
53
|
+
out[0] = '\0';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
SDL_Locale *locales = SDL_GetPreferredLocales();
|
|
57
|
+
if (locales == NULL) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
size_t length = 0;
|
|
62
|
+
for (const SDL_Locale *locale = locales; locale->language != NULL; locale++) {
|
|
63
|
+
length = rgame_locale_append(out, capacity, length, locale->language, locale->country);
|
|
64
|
+
}
|
|
65
|
+
SDL_free(locales);
|
|
66
|
+
return length;
|
|
67
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#ifndef RGAME_LOCALE_H
|
|
2
|
+
#define RGAME_LOCALE_H
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* The pure half of reading the user's preferred locales: joining them into one
|
|
8
|
+
* "de-AT,en" string in a caller's buffer. No SDL, so the Check suite covers the
|
|
9
|
+
* truncation contract on every platform, whatever locales the machine it runs
|
|
10
|
+
* on happens to prefer. locale.c's rgame_preferred_locales is the SDL shim
|
|
11
|
+
* that feeds it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Appends one locale to a list whose full length so far is `length`: a comma
|
|
16
|
+
* unless the list is empty, then `language`, then "-" and `country` when a
|
|
17
|
+
* country is given. Returns the list's new full length.
|
|
18
|
+
*
|
|
19
|
+
* It behaves like snprintf: `out` receives as much as fits in `capacity`,
|
|
20
|
+
* always NUL-terminated when `capacity` is non-zero, and the return value is
|
|
21
|
+
* the length the whole list needs, so a result >= `capacity` means it was
|
|
22
|
+
* truncated. `length` may already exceed `capacity`. A NULL or empty
|
|
23
|
+
* `language` appends nothing.
|
|
24
|
+
*/
|
|
25
|
+
size_t rgame_locale_append(char *out, size_t capacity, size_t length, const char *language,
|
|
26
|
+
const char *country);
|
|
27
|
+
|
|
28
|
+
#endif /* RGAME_LOCALE_H */
|
|
@@ -44,6 +44,20 @@
|
|
|
44
44
|
struct rgame_audio {
|
|
45
45
|
ma_resource_manager resources;
|
|
46
46
|
ma_engine engine;
|
|
47
|
+
/*
|
|
48
|
+
* Set only on Windows, only when construction fell back to `noDevice`
|
|
49
|
+
* because there was nothing to open — see create_audio. Exists so
|
|
50
|
+
* rgame_audio_backend can still answer "Null" for this engine, matching
|
|
51
|
+
* what every other no-hardware case on every platform already reports,
|
|
52
|
+
* even though this engine has no `ma_device` at all for the normal
|
|
53
|
+
* lookup to find.
|
|
54
|
+
*/
|
|
55
|
+
int forced_no_device;
|
|
56
|
+
/*
|
|
57
|
+
* How many things still need this engine: the owner counts as one, and so
|
|
58
|
+
* does every sample and song loaded from it. See audio_release.
|
|
59
|
+
*/
|
|
60
|
+
int refs;
|
|
47
61
|
};
|
|
48
62
|
|
|
49
63
|
struct rgame_sample {
|
|
@@ -131,6 +145,43 @@ static rgame_audio *create_audio(int offline, unsigned int sample_rate, char *er
|
|
|
131
145
|
* no sound card — a build server, a container — gets a working engine that
|
|
132
146
|
* plays silence rather than an error a game has to handle. Verified: with
|
|
133
147
|
* ALSA and PulseAudio unavailable, the chosen backend is "Null".
|
|
148
|
+
*
|
|
149
|
+
* That fallback is *inside* the real backend's own device-open attempt —
|
|
150
|
+
* it only helps if opening fails cleanly. Verified on Linux, which is the
|
|
151
|
+
* one platform where it demonstrably does, and where an earlier attempt to
|
|
152
|
+
* generalise the workaround below broke a leg that had been working.
|
|
153
|
+
*
|
|
154
|
+
* On Windows, a machine with zero playback devices at all (every GitHub
|
|
155
|
+
* Actions Windows runner, among others: confirmed via
|
|
156
|
+
* `Get-CimInstance Win32_SoundDevice`) crashes instead of failing inside
|
|
157
|
+
* WASAPI's default-device lookup, so the fallback above never gets a
|
|
158
|
+
* chance to run.
|
|
159
|
+
*
|
|
160
|
+
* Windows alone gets an extra step first: enumerate devices (read-only,
|
|
161
|
+
* does not attempt to open anything, so it does not hit whatever WASAPI's
|
|
162
|
+
* own open path gets wrong with no endpoint to find) and, when there are
|
|
163
|
+
* none, fall back to `noDevice` — the same mode the offline constructor
|
|
164
|
+
* below uses — rather than the auto-detect list's own real-backend open
|
|
165
|
+
* attempt.
|
|
166
|
+
*
|
|
167
|
+
* macOS looks like the same bug and is not, which is worth stating because
|
|
168
|
+
* extending this branch to it does nothing. A device-less Mac still
|
|
169
|
+
* enumerates a playback device, so a zero-device test never fires there;
|
|
170
|
+
* its equivalent crash is CoreAudio being initialised in a **forked
|
|
171
|
+
* child**, which macOS forbids outright. That belongs to the test harness
|
|
172
|
+
* rather than here — see test/test_main.c.
|
|
173
|
+
*
|
|
174
|
+
* An *explicit* null-backend device was tried first and rejected: it still
|
|
175
|
+
* spins up a real background device thread, and that thread crashed inside
|
|
176
|
+
* a Ruby process in a way the plain-C Check build of the same code never
|
|
177
|
+
* did. `noDevice` sidesteps the question entirely: with no device and no
|
|
178
|
+
* background thread, there is nothing left that could crash the way a
|
|
179
|
+
* thread can.
|
|
180
|
+
*
|
|
181
|
+
* Linux is deliberately excluded rather than merely untested. Generalising
|
|
182
|
+
* this to every platform is what broke its leg once already, and its own
|
|
183
|
+
* fallback is proven — so the rule here is that a platform opts in on the
|
|
184
|
+
* strength of a measured crash, not on the logic looking harmless.
|
|
134
185
|
*/
|
|
135
186
|
ma_engine_config engine = ma_engine_config_init();
|
|
136
187
|
engine.pResourceManager = &audio->resources;
|
|
@@ -141,6 +192,24 @@ static rgame_audio *create_audio(int offline, unsigned int sample_rate, char *er
|
|
|
141
192
|
engine.channels = 2;
|
|
142
193
|
engine.sampleRate = sample_rate;
|
|
143
194
|
}
|
|
195
|
+
#ifdef _WIN32
|
|
196
|
+
else {
|
|
197
|
+
ma_context probe;
|
|
198
|
+
if (ma_context_init(NULL, 0, NULL, &probe) == MA_SUCCESS) {
|
|
199
|
+
ma_uint32 playback_count = 0;
|
|
200
|
+
ma_result enum_result =
|
|
201
|
+
ma_context_get_devices(&probe, NULL, &playback_count, NULL, NULL);
|
|
202
|
+
ma_context_uninit(&probe);
|
|
203
|
+
|
|
204
|
+
if (enum_result == MA_SUCCESS && playback_count == 0) {
|
|
205
|
+
engine.noDevice = MA_TRUE;
|
|
206
|
+
engine.channels = 2;
|
|
207
|
+
engine.sampleRate = 44100;
|
|
208
|
+
audio->forced_no_device = 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
#endif
|
|
144
213
|
|
|
145
214
|
if (ma_engine_init(&engine, &audio->engine) != MA_SUCCESS) {
|
|
146
215
|
ma_resource_manager_uninit(&audio->resources);
|
|
@@ -149,6 +218,8 @@ static rgame_audio *create_audio(int offline, unsigned int sample_rate, char *er
|
|
|
149
218
|
return NULL;
|
|
150
219
|
}
|
|
151
220
|
|
|
221
|
+
/* The caller's own claim. Sounds add theirs as they are loaded. */
|
|
222
|
+
audio->refs = 1;
|
|
152
223
|
return audio;
|
|
153
224
|
}
|
|
154
225
|
|
|
@@ -170,8 +241,28 @@ unsigned int rgame_audio_read(rgame_audio *audio, float *out, unsigned int frame
|
|
|
170
241
|
return (unsigned int)read;
|
|
171
242
|
}
|
|
172
243
|
|
|
173
|
-
|
|
174
|
-
|
|
244
|
+
/*
|
|
245
|
+
* Drops one claim on the device, and tears it down when the last one goes.
|
|
246
|
+
*
|
|
247
|
+
* The device has to outlive every sound made from it, and a refcount is what
|
|
248
|
+
* makes that true regardless of the order things are released in. That order is
|
|
249
|
+
* not ours to choose: Ruby's collector frees unreachable objects in whatever
|
|
250
|
+
* order it sweeps them, so a Song and the Audio it came from can be freed in
|
|
251
|
+
* the same pass with the Audio going first. Tearing down a *streaming* voice
|
|
252
|
+
* then asks the resource manager's job thread to acknowledge it — and that
|
|
253
|
+
* thread went with the engine, so the wait never returns and the process hangs
|
|
254
|
+
* rather than crashing.
|
|
255
|
+
*
|
|
256
|
+
* Marking the device from the sound, which audio_ext.c does, is a different
|
|
257
|
+
* guarantee: it keeps the device alive while a sound is still *reachable*. It
|
|
258
|
+
* says nothing about two objects that became garbage together.
|
|
259
|
+
*
|
|
260
|
+
* So the owner holds a reference, every sound holds one, and the last one out
|
|
261
|
+
* does the uninit. Same shape as the refcounted texture sheet in
|
|
262
|
+
* graphics/texture.c, and for the same kind of reason.
|
|
263
|
+
*/
|
|
264
|
+
static void audio_release(rgame_audio *audio) {
|
|
265
|
+
if (!audio || --audio->refs > 0) {
|
|
175
266
|
return;
|
|
176
267
|
}
|
|
177
268
|
|
|
@@ -182,6 +273,10 @@ void rgame_audio_destroy(rgame_audio *audio) {
|
|
|
182
273
|
free(audio);
|
|
183
274
|
}
|
|
184
275
|
|
|
276
|
+
void rgame_audio_destroy(rgame_audio *audio) {
|
|
277
|
+
audio_release(audio);
|
|
278
|
+
}
|
|
279
|
+
|
|
185
280
|
void rgame_audio_set_volume(rgame_audio *audio, float volume) {
|
|
186
281
|
if (audio) {
|
|
187
282
|
ma_engine_set_volume(&audio->engine, clamp_volume(volume));
|
|
@@ -199,6 +294,16 @@ const char *rgame_audio_backend(const rgame_audio *audio) {
|
|
|
199
294
|
return "none";
|
|
200
295
|
}
|
|
201
296
|
|
|
297
|
+
/* The forced-noDevice fallback (see create_audio) has no ma_device for
|
|
298
|
+
* the normal lookup below to find, but it is standing in for exactly what
|
|
299
|
+
* the Null backend already means elsewhere — no hardware, silence — so it
|
|
300
|
+
* answers the same way rather than the more literal but less useful
|
|
301
|
+
* "none", which every other caller of this function reserves for "there
|
|
302
|
+
* is no audio object at all". */
|
|
303
|
+
if (audio->forced_no_device) {
|
|
304
|
+
return "Null";
|
|
305
|
+
}
|
|
306
|
+
|
|
202
307
|
ma_device *device = ma_engine_get_device((ma_engine *)&audio->engine);
|
|
203
308
|
return device ? ma_get_backend_name(device->pContext->backend) : "none";
|
|
204
309
|
}
|
|
@@ -293,6 +398,7 @@ rgame_sample *rgame_sample_load(rgame_audio *audio, const char *path, char *err,
|
|
|
293
398
|
strcpy(sample->path, path);
|
|
294
399
|
|
|
295
400
|
sample->audio = audio;
|
|
401
|
+
audio->refs++;
|
|
296
402
|
live_sounds++;
|
|
297
403
|
return sample;
|
|
298
404
|
}
|
|
@@ -320,8 +426,10 @@ void rgame_sample_destroy(rgame_sample *sample) {
|
|
|
320
426
|
* same file hold their own, so the last one out frees it. */
|
|
321
427
|
ma_sound_uninit(&sample->decoded);
|
|
322
428
|
free(sample->path);
|
|
429
|
+
rgame_audio *audio = sample->audio;
|
|
323
430
|
free(sample);
|
|
324
431
|
live_sounds--;
|
|
432
|
+
audio_release(audio);
|
|
325
433
|
}
|
|
326
434
|
|
|
327
435
|
void rgame_sample_play(rgame_sample *sample) {
|
|
@@ -396,6 +504,7 @@ rgame_song *rgame_song_load(rgame_audio *audio, const char *path, char *err, siz
|
|
|
396
504
|
}
|
|
397
505
|
|
|
398
506
|
song->audio = audio;
|
|
507
|
+
audio->refs++;
|
|
399
508
|
live_sounds++;
|
|
400
509
|
return song;
|
|
401
510
|
}
|
|
@@ -406,8 +515,10 @@ void rgame_song_destroy(rgame_song *song) {
|
|
|
406
515
|
}
|
|
407
516
|
|
|
408
517
|
ma_sound_uninit(&song->sound);
|
|
518
|
+
rgame_audio *audio = song->audio;
|
|
409
519
|
free(song);
|
|
410
520
|
live_sounds--;
|
|
521
|
+
audio_release(audio);
|
|
411
522
|
}
|
|
412
523
|
|
|
413
524
|
void rgame_song_play(rgame_song *song, int looping) {
|