mwolff
Port communication to access SDL in C
Hello,
write for the first time in this forum and wanted a small project to practice with ports and communication between C and Elixir. I chose SDL2, because there are enough tutorials to test it. In Elixir I only saw projects via Nif so far.
As a template I used the code from the page
https://cultivatehq.com/posts/communicating-with-c-from-elixir-using-ports/
which I have adapted accordingly for my project.
So far I only put in a few commands to show the first few tutorials of
[Lazy Foo' Productions - Beginning Game Programming v2.0]
to be able to port to Elixir
This is the C - main Code
#include "sdlCom.h"
#include "myfuncs.h"
#include "myfuncs2.h"
#define MAX_READ 1024
void process_command(char *buf, int bytes_read);
//////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
char buffer[MAX_READ];
for(;;) {
int res = poll_input();
if(res > 0) {
int len = to_read_length();
if (len > MAX_READ) err(EXIT_FAILURE, "Too large message to read.");
// len being less than zero indicates STDIN has been closed - exit
if (len < 0) return 1;
read_in(buffer, len);
process_command(buffer, len);
}
}
}
//////////////////////////////////////////////////////////////////////////////
void process_command(char *buf, int bytes_read) {
int size=0, fn;
char **param=NULL;
if (bytes_read > 0){
param=getToken(buf,':', &size);
if (!param) exit(1);
fn=atoi(param[0]);
funcPtr[fn](buf, param);
free(param);
}
else if(bytes_read < 0) {
exit(1);
}
}
Then my own Parameter - Split - Funktion
#ifndef _MYFUNCS_H
#define _MYFUNCS_H
const int SIZE = sizeof(char*);
const int MAX_PARAM = 8;
char** getToken(const char* text, const char sep, int *anz){ // meine Parameter Split Version (ähnlich strtok)
int x=0;
char** token=NULL;
char* temp = text;
token = (char**) malloc(SIZE * MAX_PARAM);
if (token) {
token[0] = temp;
while(*temp){ // ersetze alle Trennzeichen durch '\0'
if (*temp == sep){
*temp = '\0';
token[++x] = ++temp; // Zeiger in die Token-Liste einfügen eine Stelle hinter dem NULL-Zeichen
}
++temp;
}
*anz=x;
}
return token;
}
#endif
#ifndef _MYFUNCS2_H
#define _MYFUNCS2_H
#include <SDL2/SDL.h>
typedef inline void(*fPtr)(char*, char**);
inline void WMEM(char* mem, void* p){
int64_t speicher = (int64_t)p;
sprintf(mem, "%ld", speicher);
write_back(mem);
}
#define GMEM(obj, var, mem)\
SDL_##obj* var = (SDL_##obj*)atol(mem)
#define MEM(obj, var, anz)\
SDL_##obj* var = (SDL_##obj*) malloc(sizeof(SDL_##obj) * anz)
#define FUNC(name, expr) \
void name(char* memory, char** pm){ \
expr \
}
FUNC(init, int erg=SDL_Init(SDL_INIT_EVERYTHING);
WMEM(memory, erg);
)
FUNC(quit, SDL_Quit(); )
FUNC(createWindow,
SDL_Window* win = SDL_CreateWindow(pm[1], SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, atoi(pm[2]), atoi(pm[3]), SDL_WINDOW_SHOWN);
WMEM(memory, win);
)
FUNC(destroyWindow,
GMEM(Window, win, pm[1]);
SDL_DestroyWindow(win);
)
FUNC(delay, SDL_Delay(atoi(pm[1])); )
FUNC(getWindowSurface,
GMEM(Window, win, pm[1]);
SDL_Surface* surface=SDL_GetWindowSurface(win);
WMEM(memory, surface);
)
FUNC(loadBMP,
SDL_Surface* surface=SDL_LoadBMP(pm[1]);
WMEM(memory, surface);
)
FUNC(updateWindowSurface,
GMEM(Window, win, pm[1]);
SDL_UpdateWindowSurface(win);
)
FUNC(blitSurface,
GMEM(Surface, image, pm[1]);
GMEM(Rect, srcRect, pm[2]);
GMEM(Surface, screen, pm[3]);
GMEM(Rect, dstRect, pm[4]);
SDL_BlitSurface(image, srcRect, screen, dstRect);
)
FUNC(freeSurface,
GMEM(Surface, surface, pm[1]);
SDL_FreeSurface(surface);
)
FUNC(createRect,
MEM(Rect, rect, 1);//SDL_Rect* rect = (SDL_Rect*) malloc(sizeof(SDL_Rect));
rect->x=atoi(pm[1]);
rect->y=atoi(pm[2]);
rect->w=atoi(pm[3]);
rect->h=atoi(pm[4]);
WMEM(memory, rect);
)
FUNC(freeRect,
GMEM(Rect, rect, pm[1]);
free(rect);
)
FUNC(pollEvent,
SDL_Event e;
int i = SDL_PollEvent(&e);
WMEM(memory, i);
WMEM(memory, e.type);
WMEM(memory, e.key.keysym.sym);
)
FUNC(getKeyName,
const char* name = SDL_GetKeyName(atoi(pm[1]));
strcpy(memory, name); memory[strlen(name)+1]='\0';
write_back(memory);
)
fPtr funcPtr[]={init,quit,createWindow,destroyWindow,delay,getWindowSurface,
loadBMP, updateWindowSurface, blitSurface, freeSurface,
createRect, freeRect, pollEvent, getKeyName};
#endif
Elixir - Code for SDL
defmodule ESDL.Event do
def type() do
%{QUIT: 0x100, KEYDOWN: 0x300, KEYUP: 0x301,
MOUSEMOTION: 0x400, MOUSEBUTTONDOWN: 0x401,
MOUSEBUTTONUP: 0x402, MOUSEWHEEL: 0x403}
end
end
defmodule ESDL do
def initModule() do Port.open({:spawn, "./sdlCom"}, [{:packet, 2}]) end
def get(p) do
receive do
{^p, {:data, result}} -> result
end
end
def init(p) do
Port.command(p, ["0"])
List.to_integer(get(p))
end
def quit(p) do Port.command(p, ["1"]) end
def createWindow(p, title, width, height) do
Port.command(p, ["2:#{title}:#{width}:#{height}"])
get(p)
end
def destroyWindow(p, win) do Port.command(p, ["3:#{win}"]) end
def delay(p, t) do Port.command(p, ["4:#{t}"]) end
def getWindowSurface(p, win) do
Port.command(p, ["5:#{win}"])
get(p)
end
def loadBMP(p, pic) do
Port.command(p, ["6:#{pic}"])
get(p)
end
def updateWindowSurface(p, win) do Port.command(p, ["7:#{win}"]) end
def blitSurface(p, src, srcRec, dst, dstRec) do Port.command(p, ["8:#{src}:#{srcRec}:#{dst}:#{dstRec}"]) end
def freeSurface(p, surface) do Port.command(p, ["9:#{surface}"]) end
def createRect(p, x, y, width, height) do
Port.command(p, ["10:#{x}:#{y}:#{width}:#{height}"])
get(p)
end
def deletRect(p, ptrRect) do Port.command(p, ["11:#{ptrRect}"]) end
def pollEvent(p) do
Port.command(p, ["12"])
i = List.to_integer(get(p))
type = List.to_integer(get(p))
key = List.to_integer(get(p))
{i, type, key}
end
def getKeyName(p, keyCode) do
Port.command(p, ["13:#{keyCode}"])
get(p)
end
end
defmodule ESDL.Loop do
def start(p, win, image, screen, loop) do
if (loop > 0) do
ESDL.delay(p,10)
ESDL.blitSurface(p, image, NULL, screen, NULL)
ESDL.updateWindowSurface(p, win)
{_i, event, key} = ESDL.pollEvent(p)
#keyName = ESDL.getKeyName(p, key);
if (event != ESDL.Event.type()[:QUIT] && key != 32) do
if (event == ESDL.Event.type()[:KEYDOWN]) do
IO.puts "#{key} pressed!"
end
start(p, win, image, screen, loop)
end
end
end
end
p= ESDL.initModule()
result = ESDL.init(p)
if result == 0 do
win = ESDL.createWindow(p,"SDL-Test", 640, 480)
screen = ESDL.getWindowSurface(p, win)
image = ESDL.loadBMP(p,"./hello_world.bmp")
ESDL.Loop.start(p, win, image, screen, 1)
ESDL.freeSurface(p, image)
ESDL.destroyWindow(p,win)
ESDL.quit(p)
end
best regards
Michael
Most Liked
stefanchrobot
Thanks for the link @mat-hek, I drew some inspiration from your package.
The use case you mentioned could use NIFs, since you can steal the main thread, the same way that the wxWidgets bindings do (that’s what I do as well). The problem is that you still have to marshall most (all?) calls into the main thread, so there’s a lot of ceremony.
stefanchrobot
It is. On non-Macs wx_driver just creates a fresh thread instead of stealing it. Here’s the relevant excerpt from my code:
static int run_thread = 1;
static ErlDrvTid sdl_thread_id;
static void* sdl_main_loop(void* arg) {
while (run_thread) {
// Do your stuff.
}
return NULL;
}
// OTP is hiding this, but it's there.
// The functions are implemented in
//
// otp/erts/emulator/beam/erl_drv_thread.c.
//
// These are only used by wxErlang, so it declares them ad-hoc in
//
// otp/lib/wx/c_src/wxe_main.cpp
//
// and so we do the same thing.
// #ifdef __DARWIN__
int erl_drv_stolen_main_thread_join(
ErlDrvTid tid,
void** respp);
int erl_drv_steal_main_thread(
char* name,
ErlDrvTid* dtid,
void* (*func)(void*),
void* arg,
ErlDrvThreadOpts* opts);
// #endif
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) {
erl_drv_steal_main_thread(
name,
&sdl_thread_id,
sdl_main_loop,
NULL,
NULL
);
// ...
return 0;
}
The awkward part is that you now need to marshal the NIF calls into that thread. I’m using mutexes to do that. The NIF implementation is not thread-safe so you need to synchronize all NIF calls via a process. SDL is not thread-safe so you’d have to do that either way.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








