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:

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,
// 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");
}
}
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;
}
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);
- unpack the release into your project-directory
- in the inspector press "Add Child Node" and there should be a new node GDTinyCC
- 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)
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