mwolff

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

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

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.

Where Next?

Popular in Discussions Top

vans163
So useless benchmarks aside, Its possible to write a webserver that can serve 300k requests per second (perhaps more with optimizations)....
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
mmport80
I have put far too much effort into Dialyzer over the last year or so - and basically - I doubt it’s worth the effort. It’s not as easy ...
New
WildYorkies
It seems that the more I read, the more I find Elixir users speaking about all the ways that Elixir is not good for x, y, and z use cases...
New
nunobernardes99
Hi there Elixir friends :vulcan_salute: In a recent task I was on, I needed to check in two dates which of them is the maximum and which...
New
PragTob
Hey everyone, this has been brewing in my head some time and it came up again while reading Adopting Elixir. GenServers, supervisors et...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
scouten
I’m looking for a host for the server part of a small (personal) side project that I’m working on. It’s currently written in Node.js and ...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Other popular topics Top

jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement