sfml3-rb 0.0.1 → 0.2.2
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 +342 -0
- data/README.md +51 -14
- data/TODO.md +199 -0
- data/ext/audio/audio_enums.c +131 -0
- data/ext/audio/audio_enums.h +18 -0
- data/ext/audio/effect_processor.c +233 -0
- data/ext/audio/effect_processor.h +25 -0
- data/ext/audio/listener.c +83 -0
- data/ext/audio/listener.h +8 -0
- data/ext/audio/music.c +174 -0
- data/ext/audio/music.h +14 -0
- data/ext/audio/sound.c +139 -0
- data/ext/audio/sound.h +14 -0
- data/ext/audio/sound_buffer.c +274 -0
- data/ext/audio/sound_buffer.h +18 -0
- data/ext/audio/sound_buffer_recorder.c +109 -0
- data/ext/audio/sound_buffer_recorder.h +14 -0
- data/ext/audio/sound_recorder.c +268 -0
- data/ext/audio/sound_recorder.h +14 -0
- data/ext/audio/sound_source.h +13 -0
- data/ext/audio/sound_source.inc +271 -0
- data/ext/audio/sound_source_cone.c +138 -0
- data/ext/audio/sound_source_cone.h +20 -0
- data/ext/audio/sound_stream.c +294 -0
- data/ext/audio/sound_stream.h +14 -0
- data/ext/{exceptions.c → core/exceptions.c} +10 -8
- data/ext/{ext → core}/exceptions.h +3 -3
- data/ext/core/foreign_thread.c +149 -0
- data/ext/core/foreign_thread.h +31 -0
- data/ext/core/macros.h +8 -0
- data/ext/{ext → core}/sfml.h +5 -3
- data/ext/core/unicode.c +46 -0
- data/ext/core/unicode.h +25 -0
- data/ext/ext.c +125 -14
- data/ext/extconf.rb +51 -6
- data/ext/graphics/blend_mode.c +261 -0
- data/ext/graphics/blend_mode.h +18 -0
- data/ext/graphics/circle.c +319 -0
- data/ext/{ext/klass/drawable → graphics}/circle.h +3 -3
- data/ext/graphics/color.c +318 -0
- data/ext/graphics/color.h +37 -0
- data/ext/{drawable.c → graphics/drawable.c} +11 -11
- data/ext/graphics/drawable.h +10 -0
- data/ext/graphics/enums.c +123 -0
- data/ext/graphics/enums.h +20 -0
- data/ext/graphics/font.c +148 -0
- data/ext/graphics/font.h +14 -0
- data/ext/graphics/glyph.c +60 -0
- data/ext/graphics/glyph.h +14 -0
- data/ext/graphics/image.c +205 -0
- data/ext/graphics/image.h +16 -0
- data/ext/graphics/polygon.c +300 -0
- data/ext/graphics/polygon.h +10 -0
- data/ext/graphics/rect.c +287 -0
- data/ext/graphics/rect.h +30 -0
- data/ext/graphics/rectangle.c +302 -0
- data/ext/graphics/rectangle.h +10 -0
- data/ext/graphics/render_state.c +204 -0
- data/ext/graphics/render_state.h +18 -0
- data/ext/graphics/render_target.inc +181 -0
- data/ext/graphics/render_texture.c +182 -0
- data/ext/graphics/render_texture.h +14 -0
- data/ext/graphics/shader.c +540 -0
- data/ext/graphics/shader.h +14 -0
- data/ext/graphics/shape.c +311 -0
- data/ext/graphics/shape.h +10 -0
- data/ext/graphics/sprite.c +249 -0
- data/ext/graphics/sprite.h +10 -0
- data/ext/graphics/stencil_mode.c +211 -0
- data/ext/graphics/stencil_mode.h +18 -0
- data/ext/graphics/target.c +133 -0
- data/ext/graphics/target.h +40 -0
- data/ext/graphics/text.c +344 -0
- data/ext/graphics/text.h +10 -0
- data/ext/graphics/texture.c +379 -0
- data/ext/graphics/texture.h +16 -0
- data/ext/graphics/transform.c +372 -0
- data/ext/graphics/transform.h +35 -0
- data/ext/{transformable.c → graphics/transformable.c} +168 -144
- data/ext/{ext/klass → graphics}/transformable.h +3 -3
- data/ext/graphics/vertex.c +179 -0
- data/ext/graphics/vertex.h +23 -0
- data/ext/graphics/vertex_array.c +127 -0
- data/ext/graphics/vertex_array.h +10 -0
- data/ext/graphics/vertex_buffer.c +211 -0
- data/ext/graphics/vertex_buffer.h +14 -0
- data/ext/{view.c → graphics/view.c} +168 -169
- data/ext/{ext/klass → graphics}/view.h +3 -3
- data/ext/network/ftp.c +405 -0
- data/ext/network/ftp.h +20 -0
- data/ext/network/http.c +243 -0
- data/ext/network/http.h +18 -0
- data/ext/network/ip_address.c +164 -0
- data/ext/network/ip_address.h +20 -0
- data/ext/network/network_enums.c +298 -0
- data/ext/network/network_enums.h +26 -0
- data/ext/network/packet.c +189 -0
- data/ext/network/packet.h +14 -0
- data/ext/network/socket_selector.c +142 -0
- data/ext/network/socket_selector.h +14 -0
- data/ext/network/tcp_listener.c +115 -0
- data/ext/network/tcp_listener.h +14 -0
- data/ext/network/tcp_socket.c +199 -0
- data/ext/network/tcp_socket.h +17 -0
- data/ext/network/udp_socket.c +190 -0
- data/ext/network/udp_socket.h +14 -0
- data/ext/ports.rb +97 -21
- data/ext/system/buffer.c +77 -0
- data/ext/system/buffer.h +14 -0
- data/ext/system/clock.c +100 -0
- data/ext/{ext/klass → system}/clock.h +3 -3
- data/ext/system/input_stream.c +229 -0
- data/ext/system/input_stream.h +23 -0
- data/ext/system/sleep.c +16 -0
- data/ext/system/sleep.h +8 -0
- data/ext/system/time.c +218 -0
- data/ext/system/time.h +18 -0
- data/ext/system/vec2.c +251 -0
- data/ext/system/vec2.h +55 -0
- data/ext/system/vec3.c +219 -0
- data/ext/system/vec3.h +18 -0
- data/ext/window/clipboard.c +89 -0
- data/ext/window/clipboard.h +8 -0
- data/ext/window/context.c +70 -0
- data/ext/window/context.h +8 -0
- data/ext/window/context_settings.c +220 -0
- data/ext/window/context_settings.h +18 -0
- data/ext/window/cursor.c +56 -0
- data/ext/window/cursor.h +14 -0
- data/ext/window/event.c +216 -0
- data/ext/window/event.h +14 -0
- data/ext/{event_name.c → window/event_name.c} +2 -2
- data/ext/window/event_name.h +8 -0
- data/ext/window/input_enums.c +261 -0
- data/ext/window/input_enums.h +36 -0
- data/ext/window/joystick.c +60 -0
- data/ext/window/joystick.h +8 -0
- data/ext/{keyboard.c → window/keyboard.c} +74 -2
- data/ext/window/keyboard.h +12 -0
- data/ext/window/mouse.c +70 -0
- data/ext/window/mouse.h +8 -0
- data/ext/window/sensor.c +30 -0
- data/ext/window/sensor.h +8 -0
- data/ext/window/touch.c +49 -0
- data/ext/window/touch.h +8 -0
- data/ext/window/video_mode.c +133 -0
- data/ext/window/video_mode.h +14 -0
- data/ext/window/vulkan.c +44 -0
- data/ext/window/vulkan.h +8 -0
- data/ext/window/window.c +414 -0
- data/ext/{ext/klass → window}/window.h +3 -3
- data/lib/sfml/version.rb +4 -2
- data/lib/sfml.rb +2 -0
- metadata +163 -56
- data/ext/circle.c +0 -198
- data/ext/clock.c +0 -65
- data/ext/color.c +0 -43
- data/ext/event.c +0 -148
- data/ext/ext/klass/color.h +0 -26
- data/ext/ext/klass/drawable/image.h +0 -4
- data/ext/ext/klass/drawable/polygon.h +0 -4
- data/ext/ext/klass/drawable/rectangle.h +0 -4
- data/ext/ext/klass/drawable/sprite.h +0 -4
- data/ext/ext/klass/drawable/texture.h +0 -4
- data/ext/ext/klass/event/event_name.h +0 -8
- data/ext/ext/klass/event.h +0 -12
- data/ext/ext/klass/keyboard.h +0 -8
- data/ext/ext/klass/render_state.h +0 -16
- data/ext/ext/klass/target.h +0 -18
- data/ext/ext/klass/transform.h +0 -17
- data/ext/ext/klass/video_mode.h +0 -12
- data/ext/ext/module/drawable/drawable.h +0 -10
- data/ext/ext/module/transform.h +0 -18
- data/ext/ext/module.h +0 -10
- data/ext/ext/rect.h +0 -16
- data/ext/ext/style_name.h +0 -11
- data/ext/ext/vec2.h +0 -38
- data/ext/image.c +0 -5
- data/ext/polygon.c +0 -5
- data/ext/rect.c +0 -26
- data/ext/rectangle.c +0 -5
- data/ext/render_state.c +0 -109
- data/ext/sprite.c +0 -5
- data/ext/style_name.c +0 -15
- data/ext/target.c +0 -106
- data/ext/texture.c +0 -5
- data/ext/transform.c +0 -107
- data/ext/vec2.c +0 -42
- data/ext/video_mode.c +0 -78
- data/ext/window.c +0 -274
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#include "audio/sound_stream.h"
|
|
2
|
+
|
|
3
|
+
#include <ruby.h>
|
|
4
|
+
#include <ruby/thread.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
#include <stdlib.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
|
|
9
|
+
#include "audio/audio_enums.h"
|
|
10
|
+
#include "audio/effect_processor.h"
|
|
11
|
+
#include "audio/sound_source.h"
|
|
12
|
+
#include "core/exceptions.h"
|
|
13
|
+
#include "core/foreign_thread.h"
|
|
14
|
+
#include "system/time.h"
|
|
15
|
+
|
|
16
|
+
#define SOUND_STREAM_CALLBACK_TIMEOUT_MS 50
|
|
17
|
+
|
|
18
|
+
/* A subclassable stream. SFML calls the two C callbacks from its audio thread,
|
|
19
|
+
which was never created by Ruby, so calling back into Ruby directly (e.g.
|
|
20
|
+
via rb_thread_call_with_gvl) is a fatal VM error - only a genuine Ruby
|
|
21
|
+
thread may reacquire the GVL that way. Each callback instead hands off to
|
|
22
|
+
the shared foreign-thread worker (core/foreign_thread.h) and reports a
|
|
23
|
+
timeout or exception as "stop the stream" rather than unwinding into the
|
|
24
|
+
audio engine. The sample buffer lives in the wrapper and is reused across
|
|
25
|
+
callbacks, because SFML keeps the pointer it is handed until the next
|
|
26
|
+
onGetData. */
|
|
27
|
+
typedef struct {
|
|
28
|
+
SoundSource source;
|
|
29
|
+
VALUE rb_self;
|
|
30
|
+
int16_t* samples;
|
|
31
|
+
size_t samples_capacity;
|
|
32
|
+
} SoundStream;
|
|
33
|
+
|
|
34
|
+
static VALUE rb_cSoundStream;
|
|
35
|
+
|
|
36
|
+
static void SoundStream_mark(void* ptr) {
|
|
37
|
+
SoundStream* stream = ptr;
|
|
38
|
+
|
|
39
|
+
rb_gc_mark(stream->rb_self);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* sfSoundStream_destroy() can block waiting on the audio thread, the same as
|
|
43
|
+
Sound_free/SS_METHOD(stop); release the GVL for the same reason. */
|
|
44
|
+
static void* SoundStream_destroy_without_gvl(void* handle) {
|
|
45
|
+
sfSoundStream_destroy(handle);
|
|
46
|
+
return NULL;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static void SoundStream_free(void* ptr) {
|
|
50
|
+
SoundStream* stream = ptr;
|
|
51
|
+
|
|
52
|
+
effect_processor_release(stream->source.effect_slot);
|
|
53
|
+
rb_thread_call_without_gvl(SoundStream_destroy_without_gvl, stream->source.handle, RUBY_UBF_IO,
|
|
54
|
+
NULL);
|
|
55
|
+
free(stream->samples);
|
|
56
|
+
free(stream);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static const rb_data_type_t SoundStream_data_type = {
|
|
60
|
+
.wrap_struct_name = "SFML::SoundStream",
|
|
61
|
+
.function = {.dmark = SoundStream_mark, .dfree = SoundStream_free, .dsize = NULL},
|
|
62
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY};
|
|
63
|
+
|
|
64
|
+
/* Expands the stream's sample buffer if needed, then copies either a packed
|
|
65
|
+
String of int16 or an Array of Integers into it. Returns the sample count. */
|
|
66
|
+
static size_t SoundStream_store_samples(SoundStream* stream, VALUE rb_samples) {
|
|
67
|
+
size_t count;
|
|
68
|
+
|
|
69
|
+
if (RB_TYPE_P(rb_samples, T_STRING)) {
|
|
70
|
+
size_t bytes = (size_t)RSTRING_LEN(rb_samples);
|
|
71
|
+
|
|
72
|
+
count = bytes / sizeof(int16_t);
|
|
73
|
+
} else if (RB_TYPE_P(rb_samples, T_ARRAY)) {
|
|
74
|
+
count = (size_t)RARRAY_LEN(rb_samples);
|
|
75
|
+
} else {
|
|
76
|
+
rb_raise(rb_eTypeError,
|
|
77
|
+
"on_get_data must return an Array of samples, a packed String, or nil");
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (count > stream->samples_capacity) {
|
|
82
|
+
int16_t* grown = realloc(stream->samples, count * sizeof(int16_t));
|
|
83
|
+
|
|
84
|
+
if (grown == NULL) {
|
|
85
|
+
rb_raise(rb_eNoMemError, "could not grow sound stream buffer");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
stream->samples = grown;
|
|
89
|
+
stream->samples_capacity = count;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (RB_TYPE_P(rb_samples, T_STRING)) {
|
|
93
|
+
const int16_t* src = (const int16_t*)RSTRING_PTR(rb_samples);
|
|
94
|
+
size_t i;
|
|
95
|
+
|
|
96
|
+
for (i = 0; i < count; i++) {
|
|
97
|
+
stream->samples[i] = src[i];
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
size_t i;
|
|
101
|
+
|
|
102
|
+
for (i = 0; i < count; i++) {
|
|
103
|
+
stream->samples[i] = (int16_t)NUM2INT(rb_ary_entry(rb_samples, (long)i));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return count;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
typedef struct {
|
|
111
|
+
SoundStream* stream;
|
|
112
|
+
sfSoundStreamChunk* chunk;
|
|
113
|
+
int ok;
|
|
114
|
+
} GetDataContext;
|
|
115
|
+
|
|
116
|
+
static VALUE SoundStream_get_data_body(VALUE raw) {
|
|
117
|
+
GetDataContext* ctx = (GetDataContext*)raw;
|
|
118
|
+
VALUE result = rb_funcall(ctx->stream->rb_self, rb_intern("on_get_data"), 0);
|
|
119
|
+
|
|
120
|
+
if (NIL_P(result) || result == Qfalse) {
|
|
121
|
+
ctx->ok = 0;
|
|
122
|
+
return Qnil;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ctx->chunk->sampleCount = (unsigned int)SoundStream_store_samples(ctx->stream, result);
|
|
126
|
+
ctx->chunk->samples = ctx->stream->samples;
|
|
127
|
+
ctx->ok = 1;
|
|
128
|
+
|
|
129
|
+
return Qnil;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static void SoundStream_get_data_run(void* raw) {
|
|
133
|
+
int state = 0;
|
|
134
|
+
|
|
135
|
+
rb_protect(SoundStream_get_data_body, (VALUE)raw, &state);
|
|
136
|
+
|
|
137
|
+
if (state) {
|
|
138
|
+
rb_set_errinfo(Qnil);
|
|
139
|
+
((GetDataContext*)raw)->ok = 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static bool SoundStream_on_get_data(sfSoundStreamChunk* chunk, void* userData) {
|
|
144
|
+
SoundStream* stream = userData;
|
|
145
|
+
GetDataContext ctx = {.stream = stream, .chunk = chunk, .ok = 0};
|
|
146
|
+
|
|
147
|
+
/* On timeout ctx.ok stays 0, which reports "no data" below - the same
|
|
148
|
+
fallback already used for a raised exception. */
|
|
149
|
+
run_on_ruby_thread(SoundStream_get_data_run, &ctx, SOUND_STREAM_CALLBACK_TIMEOUT_MS);
|
|
150
|
+
|
|
151
|
+
return ctx.ok ? true : false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
typedef struct {
|
|
155
|
+
SoundStream* stream;
|
|
156
|
+
sfTime time;
|
|
157
|
+
} SeekContext;
|
|
158
|
+
|
|
159
|
+
static VALUE SoundStream_seek_body(VALUE raw) {
|
|
160
|
+
SeekContext* ctx = (SeekContext*)raw;
|
|
161
|
+
|
|
162
|
+
rb_funcall(ctx->stream->rb_self, rb_intern("on_seek"), 1, time_to_rb(ctx->time));
|
|
163
|
+
|
|
164
|
+
return Qnil;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static void SoundStream_seek_run(void* raw) {
|
|
168
|
+
int state = 0;
|
|
169
|
+
|
|
170
|
+
rb_protect(SoundStream_seek_body, (VALUE)raw, &state);
|
|
171
|
+
|
|
172
|
+
if (state) {
|
|
173
|
+
rb_set_errinfo(Qnil);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static void SoundStream_on_seek(sfTime time, void* userData) {
|
|
178
|
+
SoundStream* stream = userData;
|
|
179
|
+
SeekContext ctx = {.stream = stream, .time = time};
|
|
180
|
+
|
|
181
|
+
if (!rb_respond_to(stream->rb_self, rb_intern("on_seek"))) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
run_on_ruby_thread(SoundStream_seek_run, &ctx, SOUND_STREAM_CALLBACK_TIMEOUT_MS);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static VALUE SoundStream_new(int argc, VALUE* argv, VALUE klass) {
|
|
189
|
+
VALUE rb_channel_count, rb_sample_rate, rb_channel_map;
|
|
190
|
+
sfSoundChannel* channel_map = NULL;
|
|
191
|
+
size_t channel_map_size = 0;
|
|
192
|
+
SoundStream* ptr;
|
|
193
|
+
VALUE self;
|
|
194
|
+
long i;
|
|
195
|
+
|
|
196
|
+
rb_scan_args(argc, argv, "21", &rb_channel_count, &rb_sample_rate, &rb_channel_map);
|
|
197
|
+
|
|
198
|
+
if (!NIL_P(rb_channel_map)) {
|
|
199
|
+
if (!RB_TYPE_P(rb_channel_map, T_ARRAY)) {
|
|
200
|
+
rb_raise(rb_eArgError, "channel map must be an Array");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
channel_map_size = (size_t)RARRAY_LEN(rb_channel_map);
|
|
204
|
+
channel_map = malloc(sizeof(sfSoundChannel) * channel_map_size);
|
|
205
|
+
|
|
206
|
+
for (i = 0; i < (long)channel_map_size; i++) {
|
|
207
|
+
channel_map[i] = sound_channel_from_rb(rb_ary_entry(rb_channel_map, i));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
ptr = malloc(sizeof(SoundStream));
|
|
212
|
+
ptr->source.handle = NULL;
|
|
213
|
+
ptr->source.effect_slot = -1;
|
|
214
|
+
ptr->rb_self = Qnil;
|
|
215
|
+
ptr->samples = NULL;
|
|
216
|
+
ptr->samples_capacity = 0;
|
|
217
|
+
|
|
218
|
+
self = TypedData_Wrap_Struct(klass, &SoundStream_data_type, ptr);
|
|
219
|
+
ptr->rb_self = self;
|
|
220
|
+
|
|
221
|
+
if (!rb_respond_to(self, rb_intern("on_get_data"))) {
|
|
222
|
+
/* The wrapper is already owned by Ruby; its dfree tolerates a NULL
|
|
223
|
+
handle, so raising here just leaves it to be collected. */
|
|
224
|
+
free(channel_map);
|
|
225
|
+
rb_raise(rb_eNotImpError, "subclass must define #on_get_data");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
ptr->source.handle = sfSoundStream_create(
|
|
229
|
+
SoundStream_on_get_data, SoundStream_on_seek, (unsigned int)NUM2INT(rb_channel_count),
|
|
230
|
+
(unsigned int)NUM2INT(rb_sample_rate), channel_map, channel_map_size, ptr);
|
|
231
|
+
|
|
232
|
+
free(channel_map);
|
|
233
|
+
|
|
234
|
+
if (ptr->source.handle == NULL) {
|
|
235
|
+
rb_raise(rb_eRuntimeError, "failed to create sound stream");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return self;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
static VALUE SoundStream_channel_count(VALUE self) {
|
|
242
|
+
return UINT2NUM(sfSoundStream_getChannelCount(Get_SoundStream_Struct(self)));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static VALUE SoundStream_sample_rate(VALUE self) {
|
|
246
|
+
return UINT2NUM(sfSoundStream_getSampleRate(Get_SoundStream_Struct(self)));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static VALUE SoundStream_channel_map(VALUE self) {
|
|
250
|
+
size_t count = 0;
|
|
251
|
+
sfSoundChannel* map = sfSoundStream_getChannelMap(Get_SoundStream_Struct(self), &count);
|
|
252
|
+
VALUE rb_array;
|
|
253
|
+
size_t i;
|
|
254
|
+
|
|
255
|
+
if (map == NULL) {
|
|
256
|
+
return rb_ary_new();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
rb_array = rb_ary_new_capa((long)count);
|
|
260
|
+
|
|
261
|
+
for (i = 0; i < count; i++) {
|
|
262
|
+
rb_ary_push(rb_array, ID2SYM(rb_intern(sound_channel_name(map[i]))));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return rb_array;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#define SS_FN(name) sfSoundStream_##name
|
|
269
|
+
#define SS_METHOD(name) SoundStream_##name
|
|
270
|
+
#include "audio/sound_source.inc"
|
|
271
|
+
#undef SS_FN
|
|
272
|
+
#undef SS_METHOD
|
|
273
|
+
|
|
274
|
+
void Init_SoundStream(VALUE rb_module) {
|
|
275
|
+
rb_cSoundStream = rb_define_class_under(rb_module, "SoundStream", rb_cObject);
|
|
276
|
+
|
|
277
|
+
rb_define_singleton_method(rb_cSoundStream, "new", SoundStream_new, -1);
|
|
278
|
+
|
|
279
|
+
rb_define_method(rb_cSoundStream, "channel_count", SoundStream_channel_count, 0);
|
|
280
|
+
rb_define_method(rb_cSoundStream, "sample_rate", SoundStream_sample_rate, 0);
|
|
281
|
+
rb_define_method(rb_cSoundStream, "channel_map", SoundStream_channel_map, 0);
|
|
282
|
+
|
|
283
|
+
SoundStream_define_sound_source_methods(rb_cSoundStream);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
VALUE Get_Klass_SoundStream(void) {
|
|
287
|
+
return rb_cSoundStream;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
void* Get_SoundStream_Struct(VALUE self) {
|
|
291
|
+
SoundStream* ptr;
|
|
292
|
+
TypedData_Get_Struct(self, SoundStream, &SoundStream_data_type, ptr);
|
|
293
|
+
return ptr->source.handle;
|
|
294
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#ifndef SFML_RB_AUDIO_SOUND_STREAM_H
|
|
2
|
+
#define SFML_RB_AUDIO_SOUND_STREAM_H
|
|
3
|
+
|
|
4
|
+
#include <ruby.h>
|
|
5
|
+
|
|
6
|
+
#include "core/sfml.h"
|
|
7
|
+
|
|
8
|
+
void Init_SoundStream(VALUE rb_module);
|
|
9
|
+
|
|
10
|
+
VALUE Get_Klass_SoundStream(void);
|
|
11
|
+
|
|
12
|
+
void *Get_SoundStream_Struct(VALUE self);
|
|
13
|
+
|
|
14
|
+
#endif //SFML_RB_AUDIO_SOUND_STREAM_H
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
#include "
|
|
1
|
+
#include "core/exceptions.h"
|
|
2
|
+
|
|
3
|
+
#include <string.h>
|
|
2
4
|
|
|
3
5
|
#define MSG_LENGTH 86
|
|
4
6
|
|
|
5
7
|
void raise_invalid_argument_type(const char *type) {
|
|
6
8
|
char msg[MSG_LENGTH];
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
snprintf(msg, MSG_LENGTH, "invalid argument, expected a %s object", type);
|
|
9
11
|
|
|
10
12
|
rb_raise(rb_eArgError, "%s", msg);
|
|
11
13
|
}
|
|
@@ -18,9 +20,9 @@ void raise_invalid_arguments_excepted(int expected, size_t given) {
|
|
|
18
20
|
char msg[MSG_LENGTH];
|
|
19
21
|
|
|
20
22
|
if (expected > 0) {
|
|
21
|
-
|
|
23
|
+
snprintf(msg, MSG_LENGTH, "wrong number of arguments (given %lu, expected %i)", given, expected);
|
|
22
24
|
} else {
|
|
23
|
-
|
|
25
|
+
snprintf(msg, MSG_LENGTH, "wrong number of arguments (given %lu)", given);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
|
|
@@ -30,15 +32,15 @@ void raise_invalid_arguments_excepted(int expected, size_t given) {
|
|
|
30
32
|
void raise_invalid_array_length(size_t length) {
|
|
31
33
|
char msg[MSG_LENGTH];
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
snprintf(msg, MSG_LENGTH, "invalid array length, expected length of %lu", length);
|
|
34
36
|
|
|
35
37
|
rb_raise(rb_eArgError, "%s", msg);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
void raise_method_no_implemented(const char *method) {
|
|
39
|
-
if (strlen(method)
|
|
40
|
-
rb_raise(
|
|
41
|
+
if (method != NULL && strlen(method) > 0) {
|
|
42
|
+
rb_raise(rb_eNotImpError, "method not implemented: %s", method);
|
|
41
43
|
} else {
|
|
42
|
-
|
|
44
|
+
rb_raise(rb_eNotImpError, "method not implemented");
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#ifndef
|
|
2
|
-
#define
|
|
1
|
+
#ifndef SFML_RB_CORE_EXCEPTIONS_H
|
|
2
|
+
#define SFML_RB_CORE_EXCEPTIONS_H
|
|
3
3
|
|
|
4
4
|
#include <ruby.h>
|
|
5
5
|
|
|
@@ -13,4 +13,4 @@ void raise_invalid_array_length(size_t length);
|
|
|
13
13
|
|
|
14
14
|
void raise_method_no_implemented(const char *method);
|
|
15
15
|
|
|
16
|
-
#endif //
|
|
16
|
+
#endif //SFML_RB_CORE_EXCEPTIONS_H
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#include "core/foreign_thread.h"
|
|
2
|
+
|
|
3
|
+
#include <pthread.h>
|
|
4
|
+
#include <ruby.h>
|
|
5
|
+
#include <ruby/thread.h>
|
|
6
|
+
#include <time.h>
|
|
7
|
+
|
|
8
|
+
typedef enum {
|
|
9
|
+
FT_IDLE = 0,
|
|
10
|
+
FT_POSTED, /* job handed off, worker has not claimed it yet */
|
|
11
|
+
FT_RUNNING, /* worker has claimed the job and is running fn(ctx) */
|
|
12
|
+
FT_DONE
|
|
13
|
+
} ForeignThreadState;
|
|
14
|
+
|
|
15
|
+
typedef struct {
|
|
16
|
+
void (*fn)(void*);
|
|
17
|
+
void* ctx;
|
|
18
|
+
} ForeignJob;
|
|
19
|
+
|
|
20
|
+
static pthread_mutex_t ft_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
21
|
+
static pthread_cond_t ft_cond = PTHREAD_COND_INITIALIZER;
|
|
22
|
+
static ForeignJob* volatile ft_job = NULL;
|
|
23
|
+
static volatile ForeignThreadState ft_state = FT_IDLE;
|
|
24
|
+
static volatile int ft_shutdown = 0;
|
|
25
|
+
|
|
26
|
+
static VALUE ft_worker_main(void* unused);
|
|
27
|
+
|
|
28
|
+
void Init_ForeignThread(void) {
|
|
29
|
+
rb_thread_create(ft_worker_main, NULL);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Runs without the GVL on the worker thread. Blocks until a job is posted or
|
|
33
|
+
shutdown is requested. */
|
|
34
|
+
static void* ft_wait_for_post(void* unused) {
|
|
35
|
+
(void)unused;
|
|
36
|
+
|
|
37
|
+
pthread_mutex_lock(&ft_mutex);
|
|
38
|
+
while (ft_state != FT_POSTED && !ft_shutdown) {
|
|
39
|
+
pthread_cond_wait(&ft_cond, &ft_mutex);
|
|
40
|
+
}
|
|
41
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
42
|
+
|
|
43
|
+
return NULL;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Called by Ruby to interrupt ft_wait_for_post (e.g. at VM shutdown). */
|
|
47
|
+
static void ft_unblock(void* unused) {
|
|
48
|
+
(void)unused;
|
|
49
|
+
|
|
50
|
+
pthread_mutex_lock(&ft_mutex);
|
|
51
|
+
ft_shutdown = 1;
|
|
52
|
+
pthread_cond_broadcast(&ft_cond);
|
|
53
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static VALUE ft_worker_main(void* unused) {
|
|
57
|
+
(void)unused;
|
|
58
|
+
|
|
59
|
+
for (;;) {
|
|
60
|
+
ForeignJob* job;
|
|
61
|
+
|
|
62
|
+
rb_thread_call_without_gvl(ft_wait_for_post, NULL, ft_unblock, NULL);
|
|
63
|
+
|
|
64
|
+
pthread_mutex_lock(&ft_mutex);
|
|
65
|
+
|
|
66
|
+
if (ft_shutdown) {
|
|
67
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (ft_state != FT_POSTED) {
|
|
72
|
+
/* Spurious wake, or the caller already abandoned this job while
|
|
73
|
+
it was still only posted - nothing to do. */
|
|
74
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
job = ft_job;
|
|
79
|
+
ft_state = FT_RUNNING;
|
|
80
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
81
|
+
|
|
82
|
+
job->fn(job->ctx);
|
|
83
|
+
|
|
84
|
+
pthread_mutex_lock(&ft_mutex);
|
|
85
|
+
ft_state = FT_DONE;
|
|
86
|
+
pthread_cond_broadcast(&ft_cond);
|
|
87
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return Qnil;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
int run_on_ruby_thread(void (*fn)(void* ctx), void* ctx, unsigned long timeout_ms) {
|
|
94
|
+
ForeignJob job = {.fn = fn, .ctx = ctx};
|
|
95
|
+
struct timespec deadline;
|
|
96
|
+
int completed;
|
|
97
|
+
int use_timeout = (timeout_ms > 0);
|
|
98
|
+
|
|
99
|
+
if (use_timeout) {
|
|
100
|
+
clock_gettime(CLOCK_REALTIME, &deadline);
|
|
101
|
+
deadline.tv_nsec += (long)(timeout_ms % 1000) * 1000000L;
|
|
102
|
+
deadline.tv_sec += (time_t)(timeout_ms / 1000) + deadline.tv_nsec / 1000000000L;
|
|
103
|
+
deadline.tv_nsec %= 1000000000L;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pthread_mutex_lock(&ft_mutex);
|
|
107
|
+
|
|
108
|
+
while (ft_state != FT_IDLE) {
|
|
109
|
+
pthread_cond_wait(&ft_cond, &ft_mutex);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ft_job = &job;
|
|
113
|
+
ft_state = FT_POSTED;
|
|
114
|
+
pthread_cond_broadcast(&ft_cond);
|
|
115
|
+
|
|
116
|
+
while (ft_state != FT_DONE) {
|
|
117
|
+
if (use_timeout) {
|
|
118
|
+
int rc = pthread_cond_timedwait(&ft_cond, &ft_mutex, &deadline);
|
|
119
|
+
|
|
120
|
+
if (rc != 0 && ft_state == FT_POSTED) {
|
|
121
|
+
/* Still not claimed: safe to abandon, the worker has never
|
|
122
|
+
touched job/ctx. */
|
|
123
|
+
ft_state = FT_IDLE;
|
|
124
|
+
ft_job = NULL;
|
|
125
|
+
pthread_cond_broadcast(&ft_cond);
|
|
126
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (rc != 0) {
|
|
131
|
+
/* Already claimed (FT_RUNNING): job/ctx are in use on the
|
|
132
|
+
worker thread now, so it is no longer safe to abandon.
|
|
133
|
+
Fall back to an unbounded wait for FT_DONE. */
|
|
134
|
+
use_timeout = 0;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
pthread_cond_wait(&ft_cond, &ft_mutex);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
completed = (ft_state == FT_DONE);
|
|
142
|
+
ft_state = FT_IDLE;
|
|
143
|
+
ft_job = NULL;
|
|
144
|
+
pthread_cond_broadcast(&ft_cond);
|
|
145
|
+
|
|
146
|
+
pthread_mutex_unlock(&ft_mutex);
|
|
147
|
+
|
|
148
|
+
return completed;
|
|
149
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#ifndef SFML_RB_CORE_FOREIGN_THREAD_H
|
|
2
|
+
#define SFML_RB_CORE_FOREIGN_THREAD_H
|
|
3
|
+
|
|
4
|
+
/* SFML's audio thread is a foreign native thread Ruby never created, so it
|
|
5
|
+
cannot legally call back into Ruby: rb_thread_call_with_gvl() requires the
|
|
6
|
+
calling thread to have previously released the GVL via
|
|
7
|
+
rb_thread_call_without_gvl(), which only a genuine Ruby thread can do -
|
|
8
|
+
calling it from a foreign thread is a fatal VM error ("rb_thread_call_with_
|
|
9
|
+
gvl() is called by non-ruby thread"). This module starts one persistent
|
|
10
|
+
Ruby-owned worker thread that a foreign thread can safely hand jobs to. */
|
|
11
|
+
|
|
12
|
+
/* Starts the worker thread. Must be called once, with the GVL held, before
|
|
13
|
+
any foreign thread calls run_on_ruby_thread(). */
|
|
14
|
+
void Init_ForeignThread(void);
|
|
15
|
+
|
|
16
|
+
/* Runs fn(ctx) on the worker thread and blocks the calling (foreign) thread
|
|
17
|
+
until it completes, or until timeout_ms elapses (0 disables the timeout).
|
|
18
|
+
Safe to call from any thread, including ones Ruby has never seen.
|
|
19
|
+
|
|
20
|
+
On timeout, the job is only abandoned if the worker has not yet started
|
|
21
|
+
running it; once running, this function waits for it to finish regardless
|
|
22
|
+
of the timeout, so ctx is never touched by the worker after this function
|
|
23
|
+
returns - the timeout exists to bound waiting on a merely slow-to-schedule
|
|
24
|
+
worker (e.g. GVL contention), not to allow ctx to be freed out from under
|
|
25
|
+
an in-flight job.
|
|
26
|
+
|
|
27
|
+
Returns 1 if fn ran to completion, 0 if the job was abandoned on timeout
|
|
28
|
+
before the worker claimed it. */
|
|
29
|
+
int run_on_ruby_thread(void (*fn)(void* ctx), void* ctx, unsigned long timeout_ms);
|
|
30
|
+
|
|
31
|
+
#endif // SFML_RB_CORE_FOREIGN_THREAD_H
|
data/ext/core/macros.h
ADDED
data/ext/{ext → core}/sfml.h
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#ifndef
|
|
2
|
-
#define
|
|
1
|
+
#ifndef SFML_RB_CORE_SFML_H
|
|
2
|
+
#define SFML_RB_CORE_SFML_H
|
|
3
3
|
|
|
4
4
|
#include <CSFML/Config.h>
|
|
5
5
|
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
#include <CSFML/Graphics.h>
|
|
11
11
|
#include <CSFML/Window.h>
|
|
12
|
+
#include <CSFML/Audio.h>
|
|
13
|
+
#include <CSFML/Network.h>
|
|
12
14
|
#include <CSFML/System.h>
|
|
13
15
|
#include <CSFML/System/Vector2.h>
|
|
14
16
|
#include <CSFML/Graphics/RenderStates.h>
|
|
15
17
|
|
|
16
|
-
#endif //
|
|
18
|
+
#endif //SFML_RB_CORE_SFML_H
|
data/ext/core/unicode.c
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#include "core/unicode.h"
|
|
2
|
+
|
|
3
|
+
#include <ruby.h>
|
|
4
|
+
#include <ruby/encoding.h>
|
|
5
|
+
|
|
6
|
+
VALUE utf32_from_rb(VALUE rb_string) {
|
|
7
|
+
rb_encoding* utf8 = rb_utf8_encoding();
|
|
8
|
+
VALUE string = rb_str_export_to_enc(rb_string, utf8);
|
|
9
|
+
const char* cursor = RSTRING_PTR(string);
|
|
10
|
+
const char* end = cursor + RSTRING_LEN(string);
|
|
11
|
+
VALUE buffer;
|
|
12
|
+
sfChar32* out;
|
|
13
|
+
long length = 0;
|
|
14
|
+
|
|
15
|
+
/* One code point per byte is the worst case, plus the terminator. */
|
|
16
|
+
buffer = rb_str_tmp_new((RSTRING_LEN(string) + 1) * (long)sizeof(sfChar32));
|
|
17
|
+
out = (sfChar32*)RSTRING_PTR(buffer);
|
|
18
|
+
|
|
19
|
+
while (cursor < end) {
|
|
20
|
+
int width = 0;
|
|
21
|
+
|
|
22
|
+
out[length++] = (sfChar32)rb_enc_codepoint_len(cursor, end, &width, utf8);
|
|
23
|
+
cursor += width;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
out[length] = 0;
|
|
27
|
+
|
|
28
|
+
RB_GC_GUARD(string);
|
|
29
|
+
|
|
30
|
+
return buffer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
VALUE utf32_to_rb(const sfChar32* string) {
|
|
34
|
+
VALUE out = rb_utf8_str_new_cstr("");
|
|
35
|
+
size_t i;
|
|
36
|
+
|
|
37
|
+
if (string == NULL) {
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (i = 0; string[i] != 0; i++) {
|
|
42
|
+
rb_str_concat(out, UINT2NUM(string[i]));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return out;
|
|
46
|
+
}
|
data/ext/core/unicode.h
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#ifndef SFML_RB_CORE_UNICODE_H
|
|
2
|
+
#define SFML_RB_CORE_UNICODE_H
|
|
3
|
+
|
|
4
|
+
#include <ruby.h>
|
|
5
|
+
|
|
6
|
+
#include "core/sfml.h"
|
|
7
|
+
|
|
8
|
+
/* CSFML's plain `const char*` entry points hand the bytes to sf::String's
|
|
9
|
+
narrow-character constructor, which decodes them with the C locale -- so a
|
|
10
|
+
UTF-8 Ruby String arrives mangled. The *Unicode* entry points take UTF-32
|
|
11
|
+
instead, which is lossless, and these two functions are the bridge.
|
|
12
|
+
|
|
13
|
+
utf32_from_rb returns a hidden Ruby String owning the code units; read it
|
|
14
|
+
with UTF32_PTR and keep the VALUE alive (RB_GC_GUARD) until the sf* call has
|
|
15
|
+
returned. Ruby frees it, so an exception in between leaks nothing.
|
|
16
|
+
|
|
17
|
+
Deliberately not ALLOCV: that allocates on the stack for small sizes, so its
|
|
18
|
+
pointer cannot outlive the frame that created it. */
|
|
19
|
+
VALUE utf32_from_rb(VALUE rb_string);
|
|
20
|
+
|
|
21
|
+
#define UTF32_PTR(buffer) ((const sfChar32*)RSTRING_PTR(buffer))
|
|
22
|
+
|
|
23
|
+
VALUE utf32_to_rb(const sfChar32* string);
|
|
24
|
+
|
|
25
|
+
#endif // SFML_RB_CORE_UNICODE_H
|