aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2014-04-02 14:09:18 +0200
committerLoïc Hoguin <[email protected]>2014-04-02 14:09:18 +0200
commit77d0e9d9ca8ed451a40f9b54f20365727ad76f5e (patch)
tree018650dcdf59e7976a1ae018754fd04d4a583ee0
parent608acbb03f976b0fbf23877d8b4b6ad7529e1d53 (diff)
downloadesdl2-77d0e9d9ca8ed451a40f9b54f20365727ad76f5e.tar.gz
esdl2-77d0e9d9ca8ed451a40f9b54f20365727ad76f5e.tar.bz2
esdl2-77d0e9d9ca8ed451a40f9b54f20365727ad76f5e.zip
Add a bullet engine example
A function sdl_renderer:set_logical_size/3 has been added. All the functions relative to the window, the renderer, textures and events now run in a separate thread inside the NIF. A few helper functions and macros have been added in order to abstract this out. The code reads like it is doing call or cast to the main thread. In the case of call, the result is then sent back to the calling process as a message (Erlang side catches it directly before returning). The functions relative to SDL init and surfaces have not been threaded yet. It may still be needed from the point of view of SDL or Erlang, but it seems to work fine as it is so they were left alone for now. The bullet example originally came from my submission to Spawnfest 2011, and has been reactualized to work with a modern Erlang, and SDL2.
-rw-r--r--.gitignore1
-rw-r--r--Makefile4
-rw-r--r--c_src/esdl2.c4
-rw-r--r--c_src/esdl2.h2
-rw-r--r--c_src/nif_helpers.c186
-rw-r--r--c_src/nif_helpers.h11
-rw-r--r--c_src/sdl_events.c9
-rw-r--r--c_src/sdl_renderer.c107
-rw-r--r--c_src/sdl_texture.c23
-rw-r--r--c_src/sdl_window.c34
-rw-r--r--examples/bullet_engine/bullet.pngbin0 -> 793 bytes
-rw-r--r--examples/bullet_engine/bullet_engine.erl251
-rwxr-xr-xexamples/bullet_engine/start.sh2
-rw-r--r--src/esdl2.erl4
-rw-r--r--src/sdl_events.erl3
-rw-r--r--src/sdl_renderer.erl20
-rw-r--r--src/sdl_texture.erl3
-rw-r--r--src/sdl_window.erl3
18 files changed, 618 insertions, 49 deletions
diff --git a/.gitignore b/.gitignore
index 60ef3c4..dbe3e92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*.swp
c_src/env.mk
ebin
examples/*/*.beam
diff --git a/Makefile b/Makefile
index d47278b..ed7c196 100644
--- a/Makefile
+++ b/Makefile
@@ -17,5 +17,5 @@ all:
mkdir -p ebin/
erlc -o ebin/ src/*.erl
cd c_src && make
- erlc -o examples/hello_sdl examples/hello_sdl/*.erl
- cd examples/hello_sdl && ./start.sh
+ erlc -o examples/bullet_engine examples/bullet_engine/*.erl
+ cd examples/bullet_engine && ./start.sh
diff --git a/c_src/esdl2.c b/c_src/esdl2.c
index 460526e..13d18d0 100644
--- a/c_src/esdl2.c
+++ b/c_src/esdl2.c
@@ -13,6 +13,7 @@
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "esdl2.h"
+#include <sys/queue.h>
NIF_ATOMS(NIF_ATOM_DECL)
NIF_RESOURCES(NIF_RES_DECL)
@@ -22,11 +23,14 @@ int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
NIF_ATOMS(NIF_ATOM_INIT)
NIF_RESOURCES(NIF_RES_INIT)
+ *priv_data = nif_create_main_thread("esdl2");
+
return 0;
}
void unload(ErlNifEnv* env, void* priv_data)
{
+ nif_destroy_main_thread(priv_data);
}
static ErlNifFunc nif_funcs[] = {
diff --git a/c_src/esdl2.h b/c_src/esdl2.h
index d3c57c4..455c42d 100644
--- a/c_src/esdl2.h
+++ b/c_src/esdl2.h
@@ -82,6 +82,7 @@
A(xrel) \
A(y) \
A(yrel) \
+ A(_nif_thread_ret_)
// List of resources used by this NIF.
@@ -110,6 +111,7 @@
F(render_clear, 1) \
F(render_copy, 4) \
F(render_present, 1) \
+ F(render_set_logical_size, 3) \
F(set_render_draw_color, 5) \
/* sdl_surface */ \
F(img_load, 1) \
diff --git a/c_src/nif_helpers.c b/c_src/nif_helpers.c
new file mode 100644
index 0000000..820fef8
--- /dev/null
+++ b/c_src/nif_helpers.c
@@ -0,0 +1,186 @@
+// Copyright (c) 2014, Loïc Hoguin <[email protected]>
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#include "nif_helpers.h"
+#include <sys/queue.h>
+#include <stdarg.h>
+
+extern atom_ok;
+extern atom__nif_thread_ret_;
+
+typedef struct nif_thread_message {
+ TAILQ_ENTRY(nif_thread_message) next_entry;
+
+ ErlNifPid* from_pid;
+ void* function;
+ nif_thread_arg* args;
+} nif_thread_message;
+
+typedef TAILQ_HEAD(nif_thread_mailbox, nif_thread_message) nif_thread_mailbox;
+
+typedef struct {
+ ErlNifTid tid;
+ ErlNifMutex* lock;
+ ErlNifCond* cond;
+ nif_thread_mailbox* mailbox;
+} nif_thread_state;
+
+// Message.
+
+nif_thread_message* nif_thread_message_alloc(void* f, nif_thread_arg* args, ErlNifPid* pid)
+{
+ nif_thread_message* msg = (nif_thread_message*)enif_alloc(sizeof(nif_thread_message));
+
+ msg->from_pid = pid;
+ msg->function = f;
+ msg->args = args;
+
+ return msg;
+}
+
+void nif_thread_message_free(nif_thread_message* msg)
+{
+ enif_free(msg->from_pid);
+ enif_free(msg->args);
+ enif_free(msg);
+}
+
+// Calls and casts.
+
+ERL_NIF_TERM nif_thread_send(nif_thread_state* st, nif_thread_message* msg)
+{
+ enif_mutex_lock(st->lock);
+
+ TAILQ_INSERT_TAIL(st->mailbox, msg, next_entry);
+
+ enif_cond_signal(st->cond);
+ enif_mutex_unlock(st->lock);
+
+ return atom_ok;
+}
+
+ERL_NIF_TERM nif_thread_cast(ErlNifEnv* env, void (*f)(nif_thread_arg*), int a, ...)
+{
+ va_list ap;
+ int i;
+
+ nif_thread_arg* args = (nif_thread_arg*)enif_alloc(a * sizeof(nif_thread_arg));
+
+ va_start(ap, a);
+ for (i = 0; i < a; i++)
+ args[i] = va_arg(ap, void*);
+ va_end(ap);
+
+ nif_thread_message* msg = nif_thread_message_alloc(f, args, NULL);
+
+ return nif_thread_send((nif_thread_state*)enif_priv_data(env), msg);
+}
+
+ERL_NIF_TERM nif_thread_call(ErlNifEnv* env, ERL_NIF_TERM (*f)(ErlNifEnv*, nif_thread_arg*), int a, ...)
+{
+ va_list ap;
+ int i;
+
+ nif_thread_arg* args = (nif_thread_arg*)enif_alloc(a * sizeof(nif_thread_arg));
+
+ va_start(ap, a);
+ for (i = 0; i < a; i++)
+ args[i] = va_arg(ap, void*);
+ va_end(ap);
+
+ ErlNifPid* pid = (ErlNifPid*)enif_alloc(sizeof(ErlNifPid));
+ nif_thread_message* msg = nif_thread_message_alloc((void*)f, args, enif_self(env, pid));
+
+ return nif_thread_send((nif_thread_state*)enif_priv_data(env), msg);
+}
+
+// Main thread loop.
+
+int nif_thread_receive(nif_thread_state* st, nif_thread_message** msg)
+{
+ enif_mutex_lock(st->lock);
+
+ while (TAILQ_EMPTY(st->mailbox))
+ enif_cond_wait(st->cond, st->lock);
+
+ *msg = TAILQ_FIRST(st->mailbox);
+ TAILQ_REMOVE(st->mailbox, TAILQ_FIRST(st->mailbox), next_entry);
+
+ enif_mutex_unlock(st->lock);
+
+ if ((*msg)->function == NULL)
+ return 0;
+
+ return 1;
+}
+
+void nif_thread_handle(ErlNifEnv* env, nif_thread_state* st, nif_thread_message* msg)
+{
+ if (msg->from_pid == NULL) {
+ void (*cast)(nif_thread_arg*) = msg->function;
+ cast(msg->args);
+ } else {
+ ERL_NIF_TERM (*call)(ErlNifEnv*, nif_thread_arg*) = msg->function;
+ ERL_NIF_TERM ret = call(env, msg->args);
+
+ enif_send(NULL, msg->from_pid, env,
+ enif_make_tuple2(env, atom__nif_thread_ret_, ret));
+
+ enif_clear_env(env);
+ }
+
+ nif_thread_message_free(msg);
+}
+
+void* nif_main_thread(void* obj)
+{
+ ErlNifEnv* env = enif_alloc_env();
+ nif_thread_state* st = (nif_thread_state*)obj;
+ nif_thread_message* msg;
+
+ while (nif_thread_receive(st, &msg))
+ nif_thread_handle(env, st, msg);
+
+ return NULL;
+}
+
+// Main thread creation/destruction.
+
+void* nif_create_main_thread(char* name)
+{
+ nif_thread_state* st = (nif_thread_state*)enif_alloc(sizeof(nif_thread_state));
+
+ st->lock = enif_mutex_create("esdl2_lock");
+ st->cond = enif_cond_create("esdl2_cond");
+ st->mailbox = (nif_thread_mailbox*)enif_alloc(sizeof(nif_thread_mailbox));
+ TAILQ_INIT(st->mailbox);
+
+ enif_thread_create(name, &(st->tid), nif_main_thread, st, NULL);
+
+ return (void*)st;
+}
+
+void nif_destroy_main_thread(void* void_st)
+{
+ nif_thread_state* st = (nif_thread_state*)void_st;
+ nif_thread_message* msg = nif_thread_message_alloc(NULL, NULL, NULL);
+
+ nif_thread_send(st, msg);
+ enif_thread_join(st->tid, NULL);
+
+ enif_cond_destroy(st->cond);
+ enif_mutex_destroy(st->lock);
+ enif_free(st->mailbox);
+ enif_free(st);
+}
diff --git a/c_src/nif_helpers.h b/c_src/nif_helpers.h
index 06eb787..d359f50 100644
--- a/c_src/nif_helpers.h
+++ b/c_src/nif_helpers.h
@@ -106,4 +106,15 @@
return atom_undefined; \
}
+// Threaded NIFs.
+
+typedef void* nif_thread_arg;
+
+void* nif_create_main_thread(char*);
+ERL_NIF_TERM nif_thread_cast(ErlNifEnv*, void (*f)(nif_thread_arg*), int a, ...);
+ERL_NIF_TERM nif_thread_call(ErlNifEnv*, ERL_NIF_TERM (*f)(ErlNifEnv*, nif_thread_arg*), int a, ...);
+
+#define NIF_CAST_HANDLER(f) void f(nif_thread_arg* args)
+#define NIF_CALL_HANDLER(f) ERL_NIF_TERM f(ErlNifEnv* env, nif_thread_arg* args)
+
#endif
diff --git a/c_src/sdl_events.c b/c_src/sdl_events.c
index 4593a09..6952275 100644
--- a/c_src/sdl_events.c
+++ b/c_src/sdl_events.c
@@ -68,7 +68,9 @@ NIF_FLAGS_TO_LIST_FUNCTION(keymod_flags_to_list, Uint16, KEYMOD_FLAGS)
NIF_ENUM_TO_ATOM_FUNCTION(button_to_atom, Uint8, BUTTON_ENUM)
-NIF_FUNCTION(poll_event)
+// poll_event
+
+NIF_CALL_HANDLER(thread_poll_event)
{
SDL_Event event;
ERL_NIF_TERM map;
@@ -177,3 +179,8 @@ NIF_FUNCTION(poll_event)
return map;
}
+
+NIF_FUNCTION(poll_event)
+{
+ return nif_thread_call(env, thread_poll_event, 0);
+}
diff --git a/c_src/sdl_renderer.c b/c_src/sdl_renderer.c
index ae65436..a45b287 100644
--- a/c_src/sdl_renderer.c
+++ b/c_src/sdl_renderer.c
@@ -52,19 +52,14 @@ int map_to_rect(ErlNifEnv* env, ERL_NIF_TERM map, SDL_Rect* rect)
return 1;
}
-NIF_FUNCTION(create_renderer)
+// create_renderer
+
+NIF_CALL_HANDLER(thread_create_renderer)
{
- void* window_res;
- int index;
- Uint32 flags = 0;
SDL_Renderer* renderer;
ERL_NIF_TERM term;
- BADARG_IF(!enif_get_resource(env, argv[0], res_Window, &window_res));
- BADARG_IF(!enif_get_int(env, argv[1], &index));
- BADARG_IF(!list_to_renderer_flags(env, argv[2], &flags));
-
- renderer = SDL_CreateRenderer(NIF_RES_GET(Window, window_res), index, flags);
+ renderer = SDL_CreateRenderer(args[0], (long)args[1], (long)args[2]);
if (!renderer)
return sdl_error_tuple(env);
@@ -76,13 +71,50 @@ NIF_FUNCTION(create_renderer)
);
}
+NIF_FUNCTION(create_renderer)
+{
+ void* window_res;
+ int index;
+ Uint32 flags = 0;
+
+ BADARG_IF(!enif_get_resource(env, argv[0], res_Window, &window_res));
+ BADARG_IF(!enif_get_int(env, argv[1], &index));
+ BADARG_IF(!list_to_renderer_flags(env, argv[2], &flags));
+
+ return nif_thread_call(env, thread_create_renderer, 3,
+ NIF_RES_GET(Window, window_res), index, flags);
+}
+
+// render_clear
+
+NIF_CALL_HANDLER(thread_render_clear)
+{
+ if (SDL_RenderClear(args[0]))
+ return sdl_error_tuple(env);
+
+ return atom_ok;
+}
+
NIF_FUNCTION(render_clear)
{
void* renderer_res;
BADARG_IF(!enif_get_resource(env, argv[0], res_Renderer, &renderer_res));
- if (SDL_RenderClear(NIF_RES_GET(Renderer, renderer_res)))
+ return nif_thread_call(env, thread_render_clear, 1,
+ NIF_RES_GET(Renderer, renderer_res));
+}
+
+// render_copy
+
+NIF_CALL_HANDLER(thread_render_copy)
+{
+ int ret = SDL_RenderCopy(args[0], args[1], args[2], args[3]);
+
+ enif_free(args[2]);
+ enif_free(args[3]);
+
+ if (ret)
return sdl_error_tuple(env);
return atom_ok;
@@ -92,7 +124,7 @@ NIF_FUNCTION(render_copy)
{
void* renderer_res;
void* texture_res;
- SDL_Rect src, *srcPtr, dst, *dstPtr;
+ SDL_Rect *srcPtr, *dstPtr;
BADARG_IF(!enif_get_resource(env, argv[0], res_Renderer, &renderer_res));
BADARG_IF(!enif_get_resource(env, argv[1], res_Texture, &texture_res));
@@ -102,7 +134,7 @@ NIF_FUNCTION(render_copy)
else {
BADARG_IF(!enif_is_map(env, argv[2]));
- srcPtr = &src;
+ srcPtr = (SDL_Rect*)enif_alloc(sizeof(SDL_Rect));
if (!map_to_rect(env, argv[2], srcPtr))
return enif_make_badarg(env);
}
@@ -112,24 +144,61 @@ NIF_FUNCTION(render_copy)
else {
BADARG_IF(!enif_is_map(env, argv[3]));
- dstPtr = &dst;
+ dstPtr = (SDL_Rect*)enif_alloc(sizeof(SDL_Rect));
if (!map_to_rect(env, argv[3], dstPtr))
return enif_make_badarg(env);
}
- if (SDL_RenderCopy(NIF_RES_GET(Renderer, renderer_res), NIF_RES_GET(Texture, texture_res), srcPtr, dstPtr))
+ return nif_thread_call(env, thread_render_copy, 4,
+ NIF_RES_GET(Renderer, renderer_res), NIF_RES_GET(Texture, texture_res), srcPtr, dstPtr);
+}
+
+// render_present
+
+NIF_CAST_HANDLER(thread_render_present)
+{
+ SDL_RenderPresent(args[0]);
+}
+
+NIF_FUNCTION(render_present)
+{
+ void* renderer_res;
+
+ BADARG_IF(!enif_get_resource(env, argv[0], res_Renderer, &renderer_res));
+
+ return nif_thread_cast(env, thread_render_present, 1,
+ NIF_RES_GET(Renderer, renderer_res));
+}
+
+// render_set_logical_size
+
+NIF_CALL_HANDLER(thread_render_set_logical_size)
+{
+ if (SDL_RenderSetLogicalSize(args[0], (long)args[1], (long)args[2]))
return sdl_error_tuple(env);
return atom_ok;
}
-NIF_FUNCTION(render_present)
+NIF_FUNCTION(render_set_logical_size)
{
void* renderer_res;
+ int w, h;
BADARG_IF(!enif_get_resource(env, argv[0], res_Renderer, &renderer_res));
+ BADARG_IF(!enif_get_int(env, argv[1], &w));
+ BADARG_IF(!enif_get_int(env, argv[2], &h));
+
+ return nif_thread_call(env, thread_render_set_logical_size, 3,
+ NIF_RES_GET(Renderer, renderer_res), w, h);
+}
+
+// set_render_draw_color
- SDL_RenderPresent(NIF_RES_GET(Renderer, renderer_res));
+NIF_CALL_HANDLER(thread_set_render_draw_color)
+{
+ if (SDL_SetRenderDrawColor(args[0], (long)args[1], (long)args[2], (long)args[3], (long)args[4]))
+ return sdl_error_tuple(env);
return atom_ok;
}
@@ -147,8 +216,6 @@ NIF_FUNCTION(set_render_draw_color)
BADARG_IF(r < 0 || r > 255 || g < 0 || g > 255
|| b < 0 || b > 255 || a < 0 || a > 255);
- if (SDL_SetRenderDrawColor(NIF_RES_GET(Renderer, renderer_res), r, g, b ,a))
- return sdl_error_tuple(env);
-
- return atom_ok;
+ return nif_thread_call(env, thread_set_render_draw_color, 5,
+ NIF_RES_GET(Renderer, renderer_res), r, g, b, a);
}
diff --git a/c_src/sdl_texture.c b/c_src/sdl_texture.c
index daf5b93..4741bb6 100644
--- a/c_src/sdl_texture.c
+++ b/c_src/sdl_texture.c
@@ -19,17 +19,14 @@ void dtor_Texture(ErlNifEnv* env, void* obj)
SDL_DestroyTexture(NIF_RES_GET(Texture, obj));
}
-NIF_FUNCTION(create_texture_from_surface)
+// create_texture_from_surface
+
+NIF_CALL_HANDLER(thread_create_texture_from_surface)
{
- void* renderer_res;
- void* surface_res;
SDL_Texture* texture;
ERL_NIF_TERM term;
- BADARG_IF(!enif_get_resource(env, argv[0], res_Renderer, &renderer_res));
- BADARG_IF(!enif_get_resource(env, argv[1], res_Surface, &surface_res));
-
- texture = SDL_CreateTextureFromSurface(NIF_RES_GET(Renderer, renderer_res), NIF_RES_GET(Surface, surface_res));
+ texture = SDL_CreateTextureFromSurface(args[0], args[1]);
if (!texture)
return sdl_error_tuple(env);
@@ -40,3 +37,15 @@ NIF_FUNCTION(create_texture_from_surface)
term
);
}
+
+NIF_FUNCTION(create_texture_from_surface)
+{
+ void* renderer_res;
+ void* surface_res;
+
+ BADARG_IF(!enif_get_resource(env, argv[0], res_Renderer, &renderer_res));
+ BADARG_IF(!enif_get_resource(env, argv[1], res_Surface, &surface_res));
+
+ return nif_thread_call(env, thread_create_texture_from_surface, 2,
+ NIF_RES_GET(Renderer, renderer_res), NIF_RES_GET(Surface, surface_res));
+}
diff --git a/c_src/sdl_window.c b/c_src/sdl_window.c
index 0a130f3..9319270 100644
--- a/c_src/sdl_window.c
+++ b/c_src/sdl_window.c
@@ -37,22 +37,17 @@ void dtor_Window(ErlNifEnv* env, void* obj)
NIF_LIST_TO_FLAGS_FUNCTION(list_to_window_flags, Uint32, WINDOW_FLAGS)
-NIF_FUNCTION(create_window)
+// create_window
+
+NIF_CALL_HANDLER(thread_create_window)
{
- char title[255];
- int x, y, w, h;
- Uint32 flags = 0;
SDL_Window* window;
ERL_NIF_TERM term;
- BADARG_IF(!enif_get_string(env, argv[0], title, 255, ERL_NIF_LATIN1));
- BADARG_IF(!enif_get_int(env, argv[1], &x));
- BADARG_IF(!enif_get_int(env, argv[2], &y));
- BADARG_IF(!enif_get_int(env, argv[3], &w));
- BADARG_IF(!enif_get_int(env, argv[4], &h));
- BADARG_IF(!list_to_window_flags(env, argv[5], &flags));
+ window = SDL_CreateWindow(args[0], (long)args[1], (long)args[2], (long)args[3], (long)args[4], (long)args[5]);
+
+ enif_free(args[0]);
- window = SDL_CreateWindow(title, x, y, w, h, flags);
if (!window)
return sdl_error_tuple(env);
@@ -63,3 +58,20 @@ NIF_FUNCTION(create_window)
term
);
}
+
+NIF_FUNCTION(create_window)
+{
+ char* title = (char*)enif_alloc(255);
+ int x, y, w, h;
+ Uint32 flags = 0;
+
+ BADARG_IF(!enif_get_string(env, argv[0], title, 255, ERL_NIF_LATIN1));
+ BADARG_IF(!enif_get_int(env, argv[1], &x));
+ BADARG_IF(!enif_get_int(env, argv[2], &y));
+ BADARG_IF(!enif_get_int(env, argv[3], &w));
+ BADARG_IF(!enif_get_int(env, argv[4], &h));
+ BADARG_IF(!list_to_window_flags(env, argv[5], &flags));
+
+ return nif_thread_call(env, thread_create_window, 6,
+ title, x, y, w, h, flags);
+}
diff --git a/examples/bullet_engine/bullet.png b/examples/bullet_engine/bullet.png
new file mode 100644
index 0000000..7ceec16
--- /dev/null
+++ b/examples/bullet_engine/bullet.png
Binary files differ
diff --git a/examples/bullet_engine/bullet_engine.erl b/examples/bullet_engine/bullet_engine.erl
new file mode 100644
index 0000000..0006bcd
--- /dev/null
+++ b/examples/bullet_engine/bullet_engine.erl
@@ -0,0 +1,251 @@
+%% This is an example. Feel free to copy and reuse as you wish.
+
+-module(bullet_engine).
+-export([run/0]).
+
+run() ->
+ spawn(fun init/0).
+
+init() ->
+ ok = sdl:start([video]),
+ ok = sdl:stop_on_exit(),
+ {ok, Window} = sdl_window:create("Hello SDL", 10, 10, 500, 600, []),
+ {ok, Renderer} = sdl_renderer:create(Window, -1, [accelerated, present_vsync]),
+ ok = sdl_renderer:set_logical_size(Renderer, 500 bsl 16, 600 bsl 16),
+ {ok, Texture} = sdl_texture:create_from_file(Renderer, "bullet.png"),
+ loop(#{window=>Window, renderer=>Renderer, texture=>Texture,
+ scene=>init_scene()}).
+
+loop(State=#{scene:=Scene}) ->
+ events_loop(),
+ Scene2 = update_scene(Scene, []),
+ State2 = State#{scene:=Scene2},
+ render(State2),
+ loop(State2).
+
+events_loop() ->
+ case sdl_events:poll() of
+ false -> ok;
+ #{type:=quit} -> terminate();
+ _ -> events_loop()
+ end.
+
+render(#{renderer:=Renderer, texture:=Texture, scene:=Scene}) ->
+ ok = sdl_renderer:clear(Renderer),
+ _ = [sdl_renderer:copy(Renderer, Texture, undefined, Bullet)
+ || Bullet = #{t:=Type} <- Scene, Type =/= invisible],
+ ok = sdl_renderer:present(Renderer).
+
+terminate() ->
+ init:stop(),
+ exit(normal).
+
+%% Demo scene.
+
+init_scene() ->
+ [new_invisible(#{x=>250 bsl 16, y=>300 bsl 16, w=>0, h=>0, actions=>[
+ %% Part 1.
+ {var, w, 31},
+ {var, x, 0},
+ {loop, 30, [
+ {loop, 18, [
+ {fire, [
+ {set, speed, 2 bsl 16},
+ {set, dir, x}
+ ]},
+ {var, x, '+=', 30}
+ ]},
+ {wait, w},
+ {var, w, '+=', -1}
+ ]},
+
+ %% Part 2.
+ {var, y, 31},
+ {var, z, -3},
+ {loop, 2, [
+ {loop, 21, [
+ {loop, 18, [
+ {fire, [
+ {set, speed, 2 bsl 16},
+ {set, dir, x}
+ ]},
+ {var, x, '+=', y},
+ {wait, 1}
+ ]},
+ {var, y, '+=', z}
+ ]},
+ {var, z, '*=', -1}
+ ]},
+
+ %% Part 3.
+ {var, i, 45},
+ {var, n, 4},
+ {loop, 4, [
+ {wait, 60},
+ {fire, [
+ {set, w, 64 bsl 16},
+ {set, h, 64 bsl 16},
+ {set, dir, i},
+ {set, speed, 2 bsl 16},
+ {loop, 10, [
+ {fire, [
+ {var, j, 180},
+ {var, j, '+=', i},
+ {set, dir, j},
+ {set, speed, 10 bsl 16}
+ ]},
+ {wait, 6}
+ ]},
+ {set, speed, 0},
+ {var, w, 60},
+ {var, w, '*=', n},
+ {wait, w},
+ {var, d, 90},
+ {var, n, '+=', -4},
+ {var, n, '*=', -1},
+ {var, d, '*=', n},
+ {var, d, '+=', -225},
+ {set, dir, d},
+ {set, speed, 3 bsl 16},
+ {loop, 120, [
+ {var, d, '+=', 3},
+ {set, dir, d},
+ {wait, 1}
+ ]},
+ {loop, 120, [
+ {fire, []},
+ {loop, 3, [
+ {var, d, '+=', 3},
+ {set, dir, d},
+ {wait, 1}
+ ]}
+ ]},
+ {loop, 120, [
+ {fire, []},
+ {var, d2, 180},
+ {var, d2, '+=', d},
+ {fire, [
+ {set, dir, d2}
+ ]},
+ {loop, 3, [
+ {var, d, '+=', 3},
+ {set, dir, d},
+ {wait, 1}
+ ]}
+ ]},
+ {loop, 120, [
+ {fire, []},
+ {var, d2, 180},
+ {var, d2, '+=', d},
+ {fire, [
+ {set, dir, d2}
+ ]},
+ {loop, 2, [
+ {var, d, '+=', 3},
+ {set, dir, d},
+ {wait, 1}
+ ]}
+ ]},
+ {loop, 120, [
+ {fire, []},
+ {var, d2, 180},
+ {var, d2, '+=', d},
+ {fire, [
+ {set, dir, d2}
+ ]},
+ {var, d, '+=', 3},
+ {set, dir, d},
+ {wait, 1}
+ ]},
+ {wait, 240}
+ ]},
+ {var, i, '+=', 90},
+ {var, n, '+=', -1}
+ ]},
+
+ %% Wait for the scene to finish, then stop the VM.
+ {wait, 2640},
+ init_stop
+ ]})].
+
+update_scene([], Acc) ->
+ lists:flatten(Acc);
+%% We avoid float arithmetic where possible.
+%% For Pi we use the approximate fraction 103993/33102.
+%% We may also want to do an integer cosine and sine.
+update_scene([Bullet = #{x:=X, y:=Y, w:=W, h:=H, dir:=Dir, speed:=Speed,
+ wait:=Wait, actions:=Actions}|Tail], Acc) ->
+ A = (103993 * (Dir - 90)) / (33102 * 180),
+ X2 = X + round(Speed * math:cos(A)),
+ Y2 = Y + round(Speed * math:sin(A)),
+ if
+ Wait > 0 ->
+ update_scene(Tail, [Bullet#{x:=X2, y:=Y2, wait:=Wait - 1}|Acc]);
+ X2 > 500 bsl 16; X2 < -W; Y2 > 600 bsl 16; Y2 < -H ->
+ update_scene(Tail, Acc);
+ true ->
+ New = update_bullet(Bullet#{x:=X2, y:=Y2}, Actions, []),
+ update_scene(Tail, [New|Acc])
+ end.
+
+%% Bullet engine.
+%%
+%% The scene is (500 bsl 16)x(600 bsl 16) rendered as 500x600. We avoid floats for
+%% performance reasons so everything only goes up to 3 decimals, which is perfectly
+%% fine anyway.
+%%
+%% Execution is done frame by frame for simplicity, relying on vsync. Another
+%% advantage of doing this is that we can very easy record a replay based on user
+%% input, although we don't have any in this small demo.
+
+new_invisible(Parent) ->
+ Parent#{t=>invisible, w=>0, h=>0, dir=>0, speed=>0,
+ wait=>0, vars=>#{}}.
+
+new_bullet(Parent=#{x:=X, y:=Y, w:=W, h:=H}, Actions) ->
+ Parent#{t=>bullet, x=>X + (W div 2) - (8 bsl 16), y=>Y + (H div 2) - (8 bsl 16),
+ w=>16 bsl 16, h=>16 bsl 16, wait=>0, actions=>Actions}.
+
+update_bullet(Bullet, [], Acc) ->
+ [Bullet#{actions:=[]}|Acc];
+%% Stop the VM.
+update_bullet(Bullet, [init_stop|_], Acc) ->
+ init:stop(),
+ update_bullet(Bullet, [], Acc);
+%% Manipulate variables.
+update_bullet(Bullet=#{vars:=Vars}, [{var, V, Value}|Tail], Acc) ->
+ update_bullet(Bullet#{vars:=maps:put(V, Value, Vars)}, Tail, Acc);
+update_bullet(Bullet=#{vars:=Vars}, [{var, V, '+=', W}|Tail], Acc) when is_atom(W) ->
+ update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) + maps:get(W, Vars), Vars)}, Tail, Acc);
+update_bullet(Bullet=#{vars:=Vars}, [{var, V, '+=', W}|Tail], Acc) ->
+ update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) + W, Vars)}, Tail, Acc);
+update_bullet(Bullet=#{vars:=Vars}, [{var, V, '*=', W}|Tail], Acc) when is_atom(W) ->
+ update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) * maps:get(W, Vars), Vars)}, Tail, Acc);
+update_bullet(Bullet=#{vars:=Vars}, [{var, V, '*=', W}|Tail], Acc) ->
+ update_bullet(Bullet#{vars:=maps:put(V, maps:get(V, Vars) * W, Vars)}, Tail, Acc);
+%% Loop actions.
+%%
+%% We only unroll one iteration at a time to avoid wasting resources.
+update_bullet(Bullet, [{loop, 1, Actions}|Tail], Acc) ->
+ update_bullet(Bullet, Actions ++ Tail, Acc);
+update_bullet(Bullet, [{loop, N, Actions}|Tail], Acc) ->
+ update_bullet(Bullet, Actions ++ [{loop, N - 1, Actions}|Tail], Acc);
+%% Wait a few frames.
+update_bullet(Bullet=#{vars:=Vars}, [{wait, V}|Tail], Acc) when is_atom(V) ->
+ [Bullet#{wait:=maps:get(V, Vars), actions:=Tail}|Acc];
+update_bullet(Bullet, [{wait, N}|Tail], Acc) ->
+ [Bullet#{wait:=N, actions:=Tail}|Acc];
+%% Fire a new bullet.
+update_bullet(Bullet, [{fire, Actions}|Tail], Acc) ->
+ update_bullet(Bullet, Tail, [new_bullet(Bullet, Actions)|Acc]);
+%% Set bullet values directly.
+update_bullet(Bullet=#{vars:=Vars}, [{set, Key, V}|Tail], Acc) when is_atom(V) ->
+ update_bullet(maps:put(Key, maps:get(V, Vars), Bullet), Tail, Acc);
+update_bullet(Bullet=#{x:=X, y:=Y, w:=W, h:=H}, [{set, Key, Value}|Tail], Acc) ->
+ %% We need to reposition the bullet if the size changes,
+ %% are the bullet position is its top left corner.
+ case Key of
+ w -> update_bullet(maps:put(Key, Value, Bullet#{x:=X + ((W - Value) div 2)}), Tail, Acc);
+ h -> update_bullet(maps:put(Key, Value, Bullet#{y:=Y + ((H - Value) div 2)}), Tail, Acc);
+ _ -> update_bullet(maps:put(Key, Value, Bullet), Tail, Acc)
+ end.
diff --git a/examples/bullet_engine/start.sh b/examples/bullet_engine/start.sh
new file mode 100755
index 0000000..42f041c
--- /dev/null
+++ b/examples/bullet_engine/start.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+erl +stbt db -pa ../../ebin -eval "bullet_engine:run()."
diff --git a/src/esdl2.erl b/src/esdl2.erl
index a786685..e7d8d0a 100644
--- a/src/esdl2.erl
+++ b/src/esdl2.erl
@@ -30,6 +30,7 @@
-export([render_clear/1]).
-export([render_copy/4]).
-export([render_present/1]).
+-export([render_set_logical_size/3]).
-export([set_render_draw_color/5]).
%% sdl_surface
@@ -92,6 +93,9 @@ render_copy(_, _, _, _) ->
render_present(_) ->
erlang:nif_error({not_loaded, ?MODULE}).
+render_set_logical_size(_, _, _) ->
+ erlang:nif_error({not_loaded, ?MODULE}).
+
set_render_draw_color(_, _, _, _, _) ->
erlang:nif_error({not_loaded, ?MODULE}).
diff --git a/src/sdl_events.erl b/src/sdl_events.erl
index 2cd6c3f..a4710a5 100644
--- a/src/sdl_events.erl
+++ b/src/sdl_events.erl
@@ -17,4 +17,5 @@
-export([poll/0]).
poll() ->
- esdl2:poll_event().
+ esdl2:poll_event(),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
diff --git a/src/sdl_renderer.erl b/src/sdl_renderer.erl
index 2fff74f..0a66838 100644
--- a/src/sdl_renderer.erl
+++ b/src/sdl_renderer.erl
@@ -20,21 +20,31 @@
-export([copy/4]).
-export([present/1]).
-export([set_draw_color/5]).
+-export([set_logical_size/3]).
create(Window, Index, Flags) ->
- esdl2:create_renderer(Window, Index, Flags).
+ esdl2:create_renderer(Window, Index, Flags),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
clear(Renderer) ->
- esdl2:render_clear(Renderer).
+ esdl2:render_clear(Renderer),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
copy(Renderer, Texture) ->
- esdl2:render_copy(Renderer, Texture, undefined, undefined).
+ esdl2:render_copy(Renderer, Texture, undefined, undefined),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
copy(Renderer, Texture, SrcRect, DstRect) ->
- esdl2:render_copy(Renderer, Texture, SrcRect, DstRect).
+ esdl2:render_copy(Renderer, Texture, SrcRect, DstRect),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
present(Renderer) ->
esdl2:render_present(Renderer).
set_draw_color(Renderer, R, G, B, A) ->
- esdl2:set_render_draw_color(Renderer, R, G, B, A).
+ esdl2:set_render_draw_color(Renderer, R, G, B, A),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
+
+set_logical_size(Renderer, W, H) ->
+ esdl2:render_set_logical_size(Renderer, W, H),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
diff --git a/src/sdl_texture.erl b/src/sdl_texture.erl
index f181bc1..29c3018 100644
--- a/src/sdl_texture.erl
+++ b/src/sdl_texture.erl
@@ -22,4 +22,5 @@ create_from_file(Renderer, Filename) ->
create_from_surface(Renderer, Surface).
create_from_surface(Renderer, Surface) ->
- esdl2:create_texture_from_surface(Renderer, Surface).
+ esdl2:create_texture_from_surface(Renderer, Surface),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.
diff --git a/src/sdl_window.erl b/src/sdl_window.erl
index 649ad8a..fbb7542 100644
--- a/src/sdl_window.erl
+++ b/src/sdl_window.erl
@@ -17,4 +17,5 @@
-export([create/6]).
create(Title, X, Y, W, H, Flags) ->
- esdl2:create_window(Title, X, Y, W, H, Flags).
+ esdl2:create_window(Title, X, Y, W, H, Flags),
+ receive {'_nif_thread_ret_', Ret} -> Ret end.