diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | c_src/esdl2.c | 4 | ||||
-rw-r--r-- | c_src/esdl2.h | 2 | ||||
-rw-r--r-- | c_src/nif_helpers.c | 186 | ||||
-rw-r--r-- | c_src/nif_helpers.h | 11 | ||||
-rw-r--r-- | c_src/sdl_events.c | 9 | ||||
-rw-r--r-- | c_src/sdl_renderer.c | 107 | ||||
-rw-r--r-- | c_src/sdl_texture.c | 23 | ||||
-rw-r--r-- | c_src/sdl_window.c | 34 | ||||
-rw-r--r-- | examples/bullet_engine/bullet.png | bin | 0 -> 793 bytes | |||
-rw-r--r-- | examples/bullet_engine/bullet_engine.erl | 251 | ||||
-rwxr-xr-x | examples/bullet_engine/start.sh | 2 | ||||
-rw-r--r-- | src/esdl2.erl | 4 | ||||
-rw-r--r-- | src/sdl_events.erl | 3 | ||||
-rw-r--r-- | src/sdl_renderer.erl | 20 | ||||
-rw-r--r-- | src/sdl_texture.erl | 3 | ||||
-rw-r--r-- | src/sdl_window.erl | 3 |
18 files changed, 618 insertions, 49 deletions
@@ -1,3 +1,4 @@ +*.swp c_src/env.mk ebin examples/*/*.beam @@ -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 Binary files differnew file mode 100644 index 0000000..7ceec16 --- /dev/null +++ b/examples/bullet_engine/bullet.png 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. |