Skip to content

system-er/gdtinycc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

567 Commits
 
 
 
 
 
 
 
 

Repository files navigation

GDTinyCC - gdextension C compiler (v0.7.7)

integrates tinycc(aka TCC) into a Godot node.
compile and execute C code directly (both very fast!).
WIP (work in progress) - programmed with godot 4.5, tinycc-mob
the powerful commands godot_get_variant/godot_set_variant and godot_call/godot_call_deferred reach all properties and methods!
It's time to dust off the old Kernighan/Ritchie C bible and get it out of the cupboard.

edit your c-files with the editor of your choice
and choose the name of the .c-file in the GDTinyCC-node in inspector.
v0.7.0: now with HOT-RELOAD with function recompile.

  • compile and run c-source-file just in time (filename *.c in inspector in "Source File")
    more than one filename separated with comma, the main-file first (for example "res://src/main.c,res://src/add.c" - the add.h is included in add.c - projects with modularization possible )
  • save the compiled program in an objectfile (filename *.o in inspector in "Output Object File" -
    dont forget to press return)
  • load and run the saved program from an objectfile (filename *.o in inspector in "Input Object File" - dont forget to press return)
    the input object-file must be in the bin-directory (or use a path)
    if the objectfile is chosen, export the project and it works.
  • check the "Enable 2D Drawing" in GDTinyCC-node in inspector if you need _draw

screenshot from example res://src/demo3d.c:
Pic1

documentation:

always include gdtinycc_runtime.h: #include "gdtinycc_runtime.h"

  • GDTinyCC-commands in _bind_methods for extern:
    set_source_file(String)
    String get_source_file()
    compile_file()
    v0.7.0: recompile() // HOT-RELOAD(recompile is called from extern for example from gdscript)
    set_output_object_file(String)
    String get_output_object_file()
    set_input_object_file(String)
    String get_input_object_file()
    compile_to_object(String)
    load_object(String)
    set_enable_2d_drawing(bool)
    bool get_enable_2d_drawing()
    int get_compile_error_count()
    int get_compile_warning_count()
    String get_last_compile_error()
    String get_last_compile_warning()
    Array get_compile_errors()
    Array get_compile_warnings()
    void clear_compile_messages()

  • add the GDTinyCC-node direct in Inspector OR add it for example in gdscript from code:

extends Node

func _ready():
	print ("gdscript: ready started")
	var gdtcc = GDTinyCC.new()
	gdtcc.source_file = "res://src/helloworld.c"
	add_child(gdtcc) // adding to godot starts the compile
	print("gdscript: behind add_child(gdtcc)")
	
	var errors = gdtcc.get_compile_errors()
	var error_count = gdtcc.get_compile_error_count()
	var warnings = gdtcc.get_compile_warnings()
	if error_count > 0:
		for err in errors:
			print(err)

gets the output in godot:

gdscript: ready started
GDTinyCC started.
=== Compiling res://src/helloworld.c ===
hello world from GDTinyCC main.
=== SUCCESS: 0 error(s), 0 warning(s), 4 ms ===
gdscript: behind gdtcc.compile_file

  • GDTinyCC methods for tcc to access godot:
    _ready(self)
    _process(self, double delta)
    _physics_process(self, double delta)
    _input(self, event)
    _unhandled_input(event)
    _enter_tree()
    _exit_tree()
    _notification(what)
    _draw(self)

  • objects:
    godot_get_rendering_server()
    godot_get_engine()
    godot_get_display_server()
    godot_get_os()
    godot_get_physics_server2D()
    godot_get_physics_server3D()
    godot_get_input() // example:

    void* input = godot_get_input();
    GDExtensionVariant keycode;
    keycode.type = VARTYPE_INT;
    keycode.value.i = KEY_W;
    GDExtensionVariant result;
    godot_call(input, "is_key_pressed", 1, &keycode, &result);
    if (result.value.b) {
        godot_print("Key W is pressed");
    }
  • output:
    godot_print(string, ...) // examples:
	godot_print("helloworld");
	int i = 42;
	godot_print("integervar: %d", i);
	float f = 3.14159;
	godot_print("floatvar with 2 decimal places %.2f", f);
  • nodes:
    godot_get_node(self, path)
    godot_find_node(parentnode, name, 1) // 1=recursive
    godot_instantiate(self, scenepath)
    godot_create(classname) // v0.4 !!! add a missing class in godot_create
    godot_add_child_deferred(parent, child) godot_get_children_count(parent)
    godot_get_child_at(parent, nr) // example:
    int count = godot_get_children_count(parent);
    godot_print("children of parent: %d", count);
    for (int i = 0; i < count; i++) {
        void* child = godot_get_child_at(parent, i);
        godot_get_variant(child, "name", &v);
        godot_print("godot_get_child_at - childnr %d, childname: %s", i, v.value.s);
    }

godot_remove_child_deferred(parent, child)
godot_get_variant(node, property, &return-GDExtensionVariant) // look example
godot_set_variant(node, property, GDExtensionVariant)
godot_call(object, method_name, arg_count, args, &return-GDExtensionVariant) // look example
godot_load_resource(path, type_hint)
godot_call_deferred(node_ptr, method_name, arg_count, args) // example free a node:

	godot_call_deferred(label, "queue_free", 0, NULL);

godot_queue_free(node)
godot_get_tree(self) // for example to quit program:

	GDExtensionVariant result;
	void* tree = godot_get_tree(self);
	godot_call(tree, "quit", 0, NULL, &result);
  • v0.7.5:tilemaplayer:
    void godot_tilemaplayer_set_cell(void* tilemap, int x, int y, int source_id)
    void godot_tilemaplayer_set_cell_ex(void* tilemap, int x, int y, int source_id, int atlas_x, int atlas_y, int alternative_tile)
    int godot_tilemaplayer_get_cell_source_id(void* tilemap, int x, int y)
    tcc_Vector2i godot_tilemaplayer_get_cell_atlas_coords(void* tilemap, int x, int y)
    void godot_tilemaplayer_clear(void* tilemap)
    void godot_tilemaplayer_erase_cell(void* tilemap, int x, int y)

  • signals:
    godot_emit_signal(node, signal_name, arg_count, args)
    godot_connect(self, node, signal_name, callback_func, user_data)

  • godot random:
    godot_randf()
    godot_randi()
    godot_randf_range(from, to)
    godot_randi_range(from, to)
    godot_randomize()

  • time:
    godot_get_ticks_msec()
    godot_delay_msec(milliseconds)

  • input:
    godot_is_pressed(event)
    godot_eventcode(event) //if key gets the keycode, if mouse: 1=left, 2=right, 3=middle, ... godot_get_global_mouse_position(self)

  • v0.7.5:input action:
    int godot_is_action_pressed(const char* action_name)
    int godot_is_action_just_pressed(const char* action_name)

  • file I/O files:
    godot_file_open(path, mode)
    int godot_file_read(handle, buffer, size)
    int godot_file_write(handle, buffer, size)
    godot_file_close(handle)
    godot_file_seek(handle, position)
    long godot_file_get_position(handle)
    int godot_file_eof(handle)
    long godot_file_get_size(handle)
    int godot_file_exists(path)
    int godot_remove_file(path)

  • file I/O directories:
    int godot_directory_exists(path)
    int godot_make_dir(path)
    int godot_remove_dir(path)

  • math:
    sin()
    cos()
    tan()
    atan()
    atan2()
    sqrt()
    pow()
    floor()
    ceil()
    fabs()
    fmod()
    abs()
    log()
    log10()
    exp()
    asin(), acos()
    sinh(), cosh(), tanh()
    asinh(), acosh(), atanh()
    fmin(), fmax()
    round()
    trunc()

  • godot-math:
    godot_clamp_float(v, min, max)
    godot_clamp_int(v, min, max)
    godot_lerp_float(from, to, weight)
    godot_lerp_angle(from, to, weight)

  • 2D Drawing (if Checkbutton in GDTinyCC-node is enabled, a canvaslayer and a node2d are added to the node, then the _draw-method is working):
    godot_get_drawingnode(self)
    godot_get_drawingcanvas(self)
    godot_draw_rect(drawingnode, x, y, width, height, r, g, b, a, filled)
    godot_draw_circle(drawingnode, x, y, radius, r, g, b, a, filled)
    godot_draw_line(drawingnode, x1, y1, x2, y2, r, g, b, a, thickness)
    godot_draw_string(drawingnode, font, x, y, text, r, g, b, a, font_size)

  • collision:
    godot_get_physics_server2D()
    godot_get_physics_server3D()
    v0.7.1:int godot_check_collision(void* area_ptr, void* other_ptr) // use in _physics_process!
    v0.7.1:int godot_check_collision_3d(void* area_ptr, void* other_ptr) // use in _physics_process!
    void godot_setup_collision_shape(void* collision_shape, const char* shape_type, float param1, float param2, float param3)

  • v0.7.5:2d raycast:
    RaycastHit2D godot_raycast_2d(void* self, float from_x, float from_y, float to_x, float to_y, int collision_mask)

typedef struct { int hit; float pos_x, pos_y; float normal_x, normal_y; void* collider; } RaycastHit2D;

  • debugging:
    godot_get_class_name(object)

  • new var-type GDExtensionVariant:
    godot_free_variant(variant) // frees memory of ARRAY, DICTIONARY etc - can be used for all variants, cause only frees if needed

VARTYPE_BOOL = 1,
VARTYPE_INT = 2,
VARTYPE_FLOAT = 3,
VARTYPE_STRING = 4,
VARTYPE_VECTOR2 = 5,
VARTYPE_VECTOR2I = 6,
VARTYPE_RECT2 = 7,
VARTYPE_RECT2I = 8,
VARTYPE_VECTOR3 = 9,
VARTYPE_VECTOR3I = 10,
VARTYPE_COLOR = 20,
VARTYPE_STRING_NAME = 21,
VARTYPE_NODE_PATH = 22,
VARTYPE_RID = 23,
VARTYPE_OBJECT = 24,
VARTYPE_DICTIONARY = 27,
VARTYPE_ARRAY = 28,
VARTYPE_PACKED_BYTE = 29,

example src/main.c

// GDTinyCC main.c https://github.com/system-er/gdtinycc/tree/main
#include "stddef.h"
#include "gdtinycc_runtime.h"


float timepassed = 0;
void* sprite = NULL;


// callfunction for button-signal
void on_button_pressed(void* user_data, GDExtensionVariant arg) {
    godot_print("Button pressed");
}

// callfunction for timer-signal
void on_timeout(void* user_data) {
    godot_print("callfunction timer");
}

// the c-main-function
void main() {
    godot_print("hello world from GDTinyCC main.");
}


// _ready-function is called from godot
void _ready(void* self) {
    godot_print("GDTinyCC _ready called!");

    // get the parentnode
    void* parent = godot_get_node(self, "/root/Main");

    if (parent != NULL) {
        GDExtensionVariant v;
        godot_print("GDTinyCC main: node found");
        godot_get_variant(parent, "name", &v);
        // show the name of the parentnode
        godot_print("parentname: %s", v.value.s);
    }
    else {
        godot_print("GDTinyCC main: node not found");
    }

    // test instantiate
    void* scene = godot_instantiate(self, "res://scene_label.tscn");
    godot_add_child_deferred(parent, scene);

    // create a label from code
    void *label = godot_create("Label");
    if (!label) {
        godot_print("failed to create Label");
        return;
    }

    // test godot_call
    GDExtensionVariant args[1];
    GDExtensionVariant ret;
    args[0].type = VARTYPE_STRING;
    snprintf(args[0].value.s, sizeof(args[0].value.s), "new_labelname_from_code");
    godot_call(label, "set_name", 1, args, &ret);
    GDExtensionVariant vl;
    vl.type = VARTYPE_STRING;
    //vl = godot_get_variant(label, "name");
    //godot_print("label name: %s", vl.value.s);

    // set labeltext
    GDExtensionVariant v;
    v.type = VARTYPE_STRING;
    snprintf(v.value.s, sizeof(v.value.s), "this is a label created from code");
    godot_set_variant(label, "text", v);
    // set labelposition
    GDExtensionVariant va;
    va.type = VARTYPE_VECTOR2;
    va.value.vec2.x = 600.0f;
    va.value.vec2.y = 20.0f;
    godot_set_variant(label, "position", va);
    godot_add_child_deferred(parent, label);
    // test color
    va.type = VARTYPE_COLOR;
    va.value.color.r = 0.0f;
    va.value.color.g = 1.0f;
    va.value.color.b = 0.0f;
    va.value.color.a = 1.0f;
    godot_set_variant(label, "modulate", va);


    // get an existing Button and connect the callfunction to the signal pressed
    void* button = godot_get_node(self, "/root/Main/Button");
    godot_connect(self, button, "pressed", on_button_pressed, NULL);

    // create a timernode from code
    void* timer = godot_create("Timer");
    GDExtensionVariant vt1;
    vt1.type = VARTYPE_FLOAT;
    vt1.value.f = 3.0f;
    godot_set_variant(timer, "wait_time", vt1);
    GDExtensionVariant vt2;
    vt2.type = VARTYPE_BOOL;
    vt2.value.f = 1;
    godot_set_variant(timer, "autostart", vt2);
    godot_connect(self, timer, "timeout", on_timeout, NULL);
    godot_add_child_deferred(parent, timer);

    // test sprite2d
    sprite = godot_create("Sprite2D");
    if (!sprite) {
        godot_print("failed to create Sprite2D");
        return;
    }
    GDExtensionVariant off;
    off.type = VARTYPE_VECTOR2;
    off.value.vec2.x = 300;
    off.value.vec2.y = 200;
    godot_set_variant(sprite, "offset", off);
    godot_add_child_deferred(parent, sprite);
    //godot_print(godot_get_class_name(sprite));
    //sprite = godot_get_node(self, "Sprite2D");
    GDExtensionVariant texvar;
    texvar.type = VARTYPE_OBJECT;
    void* tex = godot_load_resource("res://icon.svg", "Texture2D");
    godot_print(godot_get_class_name(tex));
    texvar.ptr = tex;
    godot_set_variant(sprite, "texture", texvar);

    // test godot_get_rendering_server
    void* rs = godot_get_rendering_server();
    args[0].type = VARTYPE_COLOR;
    args[0].value.color.r = 0.0f;
    args[0].value.color.g = 0.0f;
    args[0].value.color.b = 0.0f;
    args[0].value.color.a = 1.0f;
    godot_call(rs, "set_default_clear_color", 1, args, &ret);

    // test RefCounted object
    void* mat = godot_create("ImageTexture");
    godot_print(godot_get_class_name(mat));

    // test 2 godot_get_variant
    GDExtensionVariant p1;
    godot_get_variant(label, "position", &p1);
    godot_print("labelposition: %.2f %.2f", p1.value.vec2.x, p1.value.vec2.y);
}

void _process(void* self, double delta) {
    timepassed += delta;
    GDExtensionVariant v;
    v.type = VARTYPE_VECTOR2;
    v.value.vec2.x = 10.0f + (10.0f * sin(timepassed * 2.0f));
    v.value.vec2.y = 10.0f + (10.0f * cos(timepassed * 1.5f));
    godot_set_variant(sprite, "position", v);

    // test input singleton with method is_key_pressed
    void* input = godot_get_input();
    GDExtensionVariant keycode;
    keycode.type = VARTYPE_INT;
    keycode.value.i = KEY_W;
    GDExtensionVariant result;
    godot_call(input, "is_key_pressed", 1, &keycode, &result);
    if (result.value.b) {
        godot_print("Key W is pressed");
    }
}


void _physics_process(void* self,double delta) {
}


void _input(void* self,void* event) {
    if (!event) {
        return;
    }

    if(godot_is_pressed(event)){
        int code = godot_eventcode(event);
        godot_print("input event: %d", code);
        
        if (code == KEY_ESCAPE) {
            GDExtensionVariant result;
            void* tree = godot_get_tree(self);
            godot_call(tree, "quit", 0, NULL, &result);
        }
        else if(code < 10){ //mousebutton
            if (code == MOUSE_BUTTON_LEFT) godot_print("mousebuttonleft");
            if (code == MOUSE_BUTTON_RIGHT) godot_print("mousebuttonright");
            if (code == MOUSE_BUTTON_MIDDLE) godot_print("mousebuttonmiddle");
            GDExtensionVariant v;
            v.type = VARTYPE_VECTOR2;
            v = godot_get_global_mouse_position(self);
            godot_print("mousepos: %.2f, %.2f", v.value.vec2.x, v.value.vec2.y);
        }
    }
}

// check "Enable 2D Drawing" in GDTinyCC-node in inspector 
void _draw(void* self) {
    godot_print("started _draw()");
    void* drawingnode = godot_get_drawingnode(self);
    if(drawingnode) {
        godot_draw_rect(drawingnode, 500.0f, 200.0f, 200.0f, 100.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1);
        godot_draw_circle(drawingnode, 800.0f, 400.0f, 100.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1);
		godot_draw_line(drawingnode, 600.0f, 200.0f, 700.0f, 600.0f, 1.0f, 0.0f, 0.0f, 1.0f, 6.0f);
		godot_draw_string(drawingnode, "default_font", 100.0f, 500.0f, "this is text from godot_draw_string", 1.0f, 1.0f, 1.0f, 1.0f, 22.0f);
    }
    else {
        godot_print("error: drawingnode not found");
    }
}

hotreload:

recompile() // HOT-RELOAD - recompile is called from extern for example from gdscript:

var gdtcc: GDTinyCC

func _ready():
	gdtcc = GDTinyCC.new()
	gdtcc.source_file = "res://src/hotreload_test.c"
	add_child(gdtcc)

func _input(event):
	if Input.is_key_pressed(KEY_R):
		print("gdscript: recompile")
		gdtcc.recompile()

// for hotreload set the variables in a struct and create two functions // _get_hotreload_vars and _set_hotreload_vars in the C-program - they are called from recompile (only the user knows the variables) - example:

// variables for hotreload with recompile
static float time = 0.0f;
static int counter = 0;

struct Vars {
    float time;
    int counter;
};

void* _get_hotreload_vars() {
    static struct Vars v = {0};
    v.time = time;
    v.counter = counter;
    return &v;
}

void _set_hotreload_vars(void* p) {
    struct Vars* v = (struct Vars*)p;
    time = v->time;
    counter = v->counter;
}

Global variables (in Project/Project Settings/Globals):

if you have for example a global node for global variables Gobals.gd

extends Node
var health = 42

you can get the globalvariable health in C:

    void* globals = godot_get_node(self, "/root/Globals");
    GDExtensionVariant vg;
    godot_get_variant(globals, "health", &vg);
    godot_print("Globals.health: %d", vg.value.i);

use the release (easy way):

  • unpack the release into your project-directory
  • in the inspector press "Add Child Node" and there should be a new node GDTinyCC

build:

  • copy tinycc-mob (i use version 0.9.28rc) into src/tinycc-mob
  • copy all files from src/tinycc-mob/include to demo/bin/include
  • build tinycc for your OS...
    (if libtcc1.a is after build in src/tinycc-mob copy to src/tinycc-mob/lib)
  • copy libtcc1.a into demo/bin/lib (from build or copy from a tinycc-mob release...)
  • copy godot-cpp version 4.5 to directory godot-cpp
  • start scons

(tested on ubuntu 24.4 and windows 11)

changelog:

v0.7.3: added output compiletime
v0.7.4: found bug crash signal 11
v0.7.5: terminated some bugs and new bridgefunctions for tilemaplayer, 2draycast and inputaction
v0.7.6: terminated some bugs
v0.7.7: terminated some bugs, queue_redraw for 2D and new demo julia.c