mwolff
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
Trending in Discussions
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
New
Hey there,
It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Quite interesting article Google brought me. Didn’t find any mentions about it here.
What do you think in general? Would you use togethe...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
:warning: Security advisory: Decimal DoS vulnerability
A vulnerability has been published for decimal where very large exponents can cau...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself.
My main conc...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
stefanchrobot
Hi, Michael! I’m currently playing around with SDL2 as well! I’ve tried both the NIF and Ports path. Both present their own challenges. Ports are going to be slower in terms of performance and this is going to be noticeable when you start making more SDL API calls. On the other hand, NIFs can crash the whole BEAM process. Additionally, some SDL API calls should happen from the main application thread, which is not something that you can easy control when calling in from an Elixir.
Have a look at unifex and bundlex.
mat-hek
Hi @mwolff, here’s the Membrane SDL video player implemented with C node. This use case wouldn’t work without main thread (at least on darwin afair).
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.
mat-hek
Nice, I read somewhere about wxWidgets stealing the main thread, but wasn’t aware someone else actually uses that
This seems not to be documented in erl_driver — OTP 29.0.2 (erts 17.0.2) though, so it’s private API, isn’t it?
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:
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.
mwolff
Hello @stefanchrobot ,
i had started with nif, but it is not yet working as it should. Every time you have to look how you change this and that with the nif library, I find it a bit cumbersome. The examples that you find are mostly designed in such a way that they always work, but not for more complicated things.
With port everything is a string, change it to a pointer and it works if you did everything right. I changed the example code with ports to function pointers, so that the function is called with the given number. No complicated if-else stories, which also runs faster.
best regards
Michael