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.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +342 -0
  3. data/README.md +51 -14
  4. data/TODO.md +199 -0
  5. data/ext/audio/audio_enums.c +131 -0
  6. data/ext/audio/audio_enums.h +18 -0
  7. data/ext/audio/effect_processor.c +233 -0
  8. data/ext/audio/effect_processor.h +25 -0
  9. data/ext/audio/listener.c +83 -0
  10. data/ext/audio/listener.h +8 -0
  11. data/ext/audio/music.c +174 -0
  12. data/ext/audio/music.h +14 -0
  13. data/ext/audio/sound.c +139 -0
  14. data/ext/audio/sound.h +14 -0
  15. data/ext/audio/sound_buffer.c +274 -0
  16. data/ext/audio/sound_buffer.h +18 -0
  17. data/ext/audio/sound_buffer_recorder.c +109 -0
  18. data/ext/audio/sound_buffer_recorder.h +14 -0
  19. data/ext/audio/sound_recorder.c +268 -0
  20. data/ext/audio/sound_recorder.h +14 -0
  21. data/ext/audio/sound_source.h +13 -0
  22. data/ext/audio/sound_source.inc +271 -0
  23. data/ext/audio/sound_source_cone.c +138 -0
  24. data/ext/audio/sound_source_cone.h +20 -0
  25. data/ext/audio/sound_stream.c +294 -0
  26. data/ext/audio/sound_stream.h +14 -0
  27. data/ext/{exceptions.c → core/exceptions.c} +10 -8
  28. data/ext/{ext → core}/exceptions.h +3 -3
  29. data/ext/core/foreign_thread.c +149 -0
  30. data/ext/core/foreign_thread.h +31 -0
  31. data/ext/core/macros.h +8 -0
  32. data/ext/{ext → core}/sfml.h +5 -3
  33. data/ext/core/unicode.c +46 -0
  34. data/ext/core/unicode.h +25 -0
  35. data/ext/ext.c +125 -14
  36. data/ext/extconf.rb +51 -6
  37. data/ext/graphics/blend_mode.c +261 -0
  38. data/ext/graphics/blend_mode.h +18 -0
  39. data/ext/graphics/circle.c +319 -0
  40. data/ext/{ext/klass/drawable → graphics}/circle.h +3 -3
  41. data/ext/graphics/color.c +318 -0
  42. data/ext/graphics/color.h +37 -0
  43. data/ext/{drawable.c → graphics/drawable.c} +11 -11
  44. data/ext/graphics/drawable.h +10 -0
  45. data/ext/graphics/enums.c +123 -0
  46. data/ext/graphics/enums.h +20 -0
  47. data/ext/graphics/font.c +148 -0
  48. data/ext/graphics/font.h +14 -0
  49. data/ext/graphics/glyph.c +60 -0
  50. data/ext/graphics/glyph.h +14 -0
  51. data/ext/graphics/image.c +205 -0
  52. data/ext/graphics/image.h +16 -0
  53. data/ext/graphics/polygon.c +300 -0
  54. data/ext/graphics/polygon.h +10 -0
  55. data/ext/graphics/rect.c +287 -0
  56. data/ext/graphics/rect.h +30 -0
  57. data/ext/graphics/rectangle.c +302 -0
  58. data/ext/graphics/rectangle.h +10 -0
  59. data/ext/graphics/render_state.c +204 -0
  60. data/ext/graphics/render_state.h +18 -0
  61. data/ext/graphics/render_target.inc +181 -0
  62. data/ext/graphics/render_texture.c +182 -0
  63. data/ext/graphics/render_texture.h +14 -0
  64. data/ext/graphics/shader.c +540 -0
  65. data/ext/graphics/shader.h +14 -0
  66. data/ext/graphics/shape.c +311 -0
  67. data/ext/graphics/shape.h +10 -0
  68. data/ext/graphics/sprite.c +249 -0
  69. data/ext/graphics/sprite.h +10 -0
  70. data/ext/graphics/stencil_mode.c +211 -0
  71. data/ext/graphics/stencil_mode.h +18 -0
  72. data/ext/graphics/target.c +133 -0
  73. data/ext/graphics/target.h +40 -0
  74. data/ext/graphics/text.c +344 -0
  75. data/ext/graphics/text.h +10 -0
  76. data/ext/graphics/texture.c +379 -0
  77. data/ext/graphics/texture.h +16 -0
  78. data/ext/graphics/transform.c +372 -0
  79. data/ext/graphics/transform.h +35 -0
  80. data/ext/{transformable.c → graphics/transformable.c} +168 -144
  81. data/ext/{ext/klass → graphics}/transformable.h +3 -3
  82. data/ext/graphics/vertex.c +179 -0
  83. data/ext/graphics/vertex.h +23 -0
  84. data/ext/graphics/vertex_array.c +127 -0
  85. data/ext/graphics/vertex_array.h +10 -0
  86. data/ext/graphics/vertex_buffer.c +211 -0
  87. data/ext/graphics/vertex_buffer.h +14 -0
  88. data/ext/{view.c → graphics/view.c} +168 -169
  89. data/ext/{ext/klass → graphics}/view.h +3 -3
  90. data/ext/network/ftp.c +405 -0
  91. data/ext/network/ftp.h +20 -0
  92. data/ext/network/http.c +243 -0
  93. data/ext/network/http.h +18 -0
  94. data/ext/network/ip_address.c +164 -0
  95. data/ext/network/ip_address.h +20 -0
  96. data/ext/network/network_enums.c +298 -0
  97. data/ext/network/network_enums.h +26 -0
  98. data/ext/network/packet.c +189 -0
  99. data/ext/network/packet.h +14 -0
  100. data/ext/network/socket_selector.c +142 -0
  101. data/ext/network/socket_selector.h +14 -0
  102. data/ext/network/tcp_listener.c +115 -0
  103. data/ext/network/tcp_listener.h +14 -0
  104. data/ext/network/tcp_socket.c +199 -0
  105. data/ext/network/tcp_socket.h +17 -0
  106. data/ext/network/udp_socket.c +190 -0
  107. data/ext/network/udp_socket.h +14 -0
  108. data/ext/ports.rb +97 -21
  109. data/ext/system/buffer.c +77 -0
  110. data/ext/system/buffer.h +14 -0
  111. data/ext/system/clock.c +100 -0
  112. data/ext/{ext/klass → system}/clock.h +3 -3
  113. data/ext/system/input_stream.c +229 -0
  114. data/ext/system/input_stream.h +23 -0
  115. data/ext/system/sleep.c +16 -0
  116. data/ext/system/sleep.h +8 -0
  117. data/ext/system/time.c +218 -0
  118. data/ext/system/time.h +18 -0
  119. data/ext/system/vec2.c +251 -0
  120. data/ext/system/vec2.h +55 -0
  121. data/ext/system/vec3.c +219 -0
  122. data/ext/system/vec3.h +18 -0
  123. data/ext/window/clipboard.c +89 -0
  124. data/ext/window/clipboard.h +8 -0
  125. data/ext/window/context.c +70 -0
  126. data/ext/window/context.h +8 -0
  127. data/ext/window/context_settings.c +220 -0
  128. data/ext/window/context_settings.h +18 -0
  129. data/ext/window/cursor.c +56 -0
  130. data/ext/window/cursor.h +14 -0
  131. data/ext/window/event.c +216 -0
  132. data/ext/window/event.h +14 -0
  133. data/ext/{event_name.c → window/event_name.c} +2 -2
  134. data/ext/window/event_name.h +8 -0
  135. data/ext/window/input_enums.c +261 -0
  136. data/ext/window/input_enums.h +36 -0
  137. data/ext/window/joystick.c +60 -0
  138. data/ext/window/joystick.h +8 -0
  139. data/ext/{keyboard.c → window/keyboard.c} +74 -2
  140. data/ext/window/keyboard.h +12 -0
  141. data/ext/window/mouse.c +70 -0
  142. data/ext/window/mouse.h +8 -0
  143. data/ext/window/sensor.c +30 -0
  144. data/ext/window/sensor.h +8 -0
  145. data/ext/window/touch.c +49 -0
  146. data/ext/window/touch.h +8 -0
  147. data/ext/window/video_mode.c +133 -0
  148. data/ext/window/video_mode.h +14 -0
  149. data/ext/window/vulkan.c +44 -0
  150. data/ext/window/vulkan.h +8 -0
  151. data/ext/window/window.c +414 -0
  152. data/ext/{ext/klass → window}/window.h +3 -3
  153. data/lib/sfml/version.rb +4 -2
  154. data/lib/sfml.rb +2 -0
  155. metadata +163 -56
  156. data/ext/circle.c +0 -198
  157. data/ext/clock.c +0 -65
  158. data/ext/color.c +0 -43
  159. data/ext/event.c +0 -148
  160. data/ext/ext/klass/color.h +0 -26
  161. data/ext/ext/klass/drawable/image.h +0 -4
  162. data/ext/ext/klass/drawable/polygon.h +0 -4
  163. data/ext/ext/klass/drawable/rectangle.h +0 -4
  164. data/ext/ext/klass/drawable/sprite.h +0 -4
  165. data/ext/ext/klass/drawable/texture.h +0 -4
  166. data/ext/ext/klass/event/event_name.h +0 -8
  167. data/ext/ext/klass/event.h +0 -12
  168. data/ext/ext/klass/keyboard.h +0 -8
  169. data/ext/ext/klass/render_state.h +0 -16
  170. data/ext/ext/klass/target.h +0 -18
  171. data/ext/ext/klass/transform.h +0 -17
  172. data/ext/ext/klass/video_mode.h +0 -12
  173. data/ext/ext/module/drawable/drawable.h +0 -10
  174. data/ext/ext/module/transform.h +0 -18
  175. data/ext/ext/module.h +0 -10
  176. data/ext/ext/rect.h +0 -16
  177. data/ext/ext/style_name.h +0 -11
  178. data/ext/ext/vec2.h +0 -38
  179. data/ext/image.c +0 -5
  180. data/ext/polygon.c +0 -5
  181. data/ext/rect.c +0 -26
  182. data/ext/rectangle.c +0 -5
  183. data/ext/render_state.c +0 -109
  184. data/ext/sprite.c +0 -5
  185. data/ext/style_name.c +0 -15
  186. data/ext/target.c +0 -106
  187. data/ext/texture.c +0 -5
  188. data/ext/transform.c +0 -107
  189. data/ext/vec2.c +0 -42
  190. data/ext/video_mode.c +0 -78
  191. 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 "ext/exceptions.h"
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
- sprintf(msg, "invalid argument, expected a %s object", type);
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
- sprintf(msg, "wrong number of arguments (given %lu, expected %i)", given, expected);
23
+ snprintf(msg, MSG_LENGTH, "wrong number of arguments (given %lu, expected %i)", given, expected);
22
24
  } else {
23
- sprintf(msg, "wrong number of arguments (given %lu)", given);
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
- sprintf(msg, "invalid array length, expected length of %lu", length);
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) == 0) {
40
- rb_raise(rb_eArgError, "%s", "method no implemented");
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 SFML_RB_EXCEPTIONS_H
2
- #define SFML_RB_EXCEPTIONS_H
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 //SFML_RB_EXCEPTIONS_H
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
@@ -0,0 +1,8 @@
1
+ #ifndef SFML_RB_CORE_MACROS_H
2
+ #define SFML_RB_CORE_MACROS_H
3
+
4
+ #include <ruby.h>
5
+
6
+ #define BOOL2RB(value) value? Qtrue: Qfalse
7
+
8
+ #endif //SFML_RB_CORE_MACROS_H
@@ -1,5 +1,5 @@
1
- #ifndef SFML_H
2
- #define SFML_H
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 //SFML_H
18
+ #endif //SFML_RB_CORE_SFML_H
@@ -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
+ }
@@ -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