Inital context stuff and tests

Linking and  building is successfull and inital tests pass
This commit is contained in:
chrisbell committed 2026-11-02 14:16:16 -06:00
1 parent 2296f68123
commit 363e9894e8
10 files changed
+392 -41

No files matched your search

+3
View File
@@ -3,3 +3,6 @@
docs/local/ docs/local/
build/ build/
build-wasm/
compile_commands.json
/.emscripten-cache
+57 -28
View File
@@ -72,9 +72,20 @@ color emoji are out of scope for the first versions.
## Rendering ## Rendering
Rectangles, borders, and glyphs all reduce to quads. A backend only needs to The core never calls a backend. Each frame `rg_render` returns a draw list
manage textures, draw quad batches, and apply a scissor rect. Rendering made of texture operations (create, update region, destroy) followed by
backends are replaceable, and the SDL3 and OpenGL backend is the default. batches of colored and textured quads with scissor rects. A backend is any
code that consumes that list. The SDL3 and OpenGL backend is the default, and
the null backend records the list for headless tests. A WASM host can consume
the list from JavaScript. The shipped backends share a small convenience
interface, and the core does not depend on it.
## Viewport and scale
All API coordinates are logical pixels. The host calls
`rg_set_viewport(ctx, w, h, scale)` when the window resizes or the display
scale changes. A scale change invalidates the glyph atlas and glyphs are
rasterized again at the new size.
## Documents ## Documents
@@ -144,27 +155,6 @@ across the FFI boundary.
never by raw pointers. never by raw pointers.
- The user can supply an allocator, and internal allocation uses arenas. - The user can supply an allocator, and internal allocation uses arenas.
## Build and environment
The build system is CMake with Ninja. Development happens on NixOS using
direnv and flakes, and the devShell provides CMake, Ninja, pkg-config, clang,
clang-tools, SDL3, Emscripten, and lua-language-server. Emscripten's cache
directory is set to `.emscripten-cache` inside the project. WASM builds go
through `emcmake` into `build-wasm/`. Sources live in a flat `src/` with no
generated files, so Zig and Odin users can compile them with their own
toolchain.
## Milestone 1
A `main.lua` builds a `VBox` of `Label` and `Button` nodes. A stylesheet with
classes and a `:hover` state styles them. Layout runs through Clay and draws
through the SDL3 and OpenGL backend with stb_truetype text. A button click
fires both a Lua handler and a named event in the polled queue. One
transition eases a button background on hover. A data-bound label shows a
counter that the button increments.
Text inputs and scrolling follow in milestone 2, along with focus handling.
## Context and ownership ## Context and ownership
All state lives in an `rg_context`, which is created with `rg_create` and All state lives in an `rg_context`, which is created with `rg_create` and
@@ -194,8 +184,47 @@ Gamepad navigation will be added as a semantic layer (up, down, left, right,
accept, cancel) that drives focus movement, so a game can map any device to accept, cancel) that drives focus movement, so a game can map any device to
it. it.
## Open questions ## Errors and logging
- Whether texture uploads travel inside the draw list. Functions that can fail return an `int` status where zero is success and
- Viewport and display scale handling. negative values are errors. `rg_last_error(ctx)` returns the message for the
- Error reporting and logging. most recent failure on that context. The config carries a log callback, a log
level, and a userdata pointer. The default callback writes to stderr and the
default level is trace, so logging is verbose until I lower it. Lua errors
such as syntax errors and handler failures are logged and never replace the
live tree, so a broken save during hot reload leaves the last good tree on
screen.
## Testing
`ctest` runs the smoke tests. The core smoke test installs a counting
allocator and fails if any bytes are live after `rg_destroy`. Every
allocation, including Lua's, goes through the context allocator.
### Build and environment
The build system is CMake with Ninja. Development happens on NixOS using
direnv and flakes, and the devShell provides CMake, Ninja, pkg-config, clang,
clang-tools, SDL3, Emscripten, and lua-language-server. Emscripten's cache
directory is set to `.emscripten-cache` inside the project. WASM builds go
through `emcmake` into `build-wasm/`. Sources live in a flat `src/` with no
generated files, so Zig and Odin users can compile them with their own
toolchain.
### Milestone 1
A `main.lua` builds a `VBox` of `Label` and `Button` nodes. A stylesheet with
classes and a `:hover` state styles them. Layout runs through Clay and draws
through the SDL3 and OpenGL backend with stb_truetype text. A button click
fires both a Lua handler and a named event in the polled queue. One
transition eases a button background on hover. A data-bound label shows a
counter that the button increments.
Text inputs and scrolling follow in milestone 2, along with focus handling.
### Open questions
- Node handle format.
- Property system: how properties are named, typed, and stored.
+10 -13
View File
@@ -6,13 +6,11 @@
Last edit date: 11-02-2026 Last edit date: 11-02-2026
*/ */
#ifndef REGOLITH_H #ifndef REGOLITH_H
#define REGOLITH_H #define REGOLITH_H
#include <cstddef> #include <stddef.h>
#include <cstdint> #include <stdint.h>
#if defined(_WIN32) && defined(RG_SHARED) #if defined(_WIN32) && defined(RG_SHARED)
#ifdef RG_BUILD #ifdef RG_BUILD
@@ -32,25 +30,26 @@ extern "C" {
#define RG_VERSION_STRING "0.0.1" #define RG_VERSION_STRING "0.0.1"
/* /*
TODO: Add documentation comments TODO: Add documentation comments
*/ */
enum { enum {
RG_LOG_TRACE = 0, RG_OK = 0,
RG_LOG_DEBUG, RG_ERR_GENERIC = -1,
RG_LOG_INFO, RG_ERR_NO_MEMORY = -2,
RG_LOG_WARN, RG_ERR_STATE = -3,
RG_LOG_ERROR RG_ERR_LUA = -4
}; };
enum { RG_LOG_TRACE = 0, RG_LOG_DEBUG, RG_LOG_INFO, RG_LOG_WARN, RG_LOG_ERROR };
typedef struct rg_context rg_context; typedef struct rg_context rg_context;
typedef struct rg_allocator { typedef struct rg_allocator {
void *(*alloc)(size_t size, void *user); void *(*alloc)(size_t size, void *user);
void *(*realloc)(void *ptr, size_t old_size, size_t new_size, void *user); void *(*realloc)(void *ptr, size_t old_size, size_t new_size, void *user);
void* (*free)(void* ptr, size_t size, void* user); void (*free)(void *ptr, size_t size, void *user);
void *user; void *user;
} rg_allocator; } rg_allocator;
@@ -67,14 +66,12 @@ typedef struct rg_config {
float display_scale; float display_scale;
} rg_config; } rg_config;
RG_API const char *rg_version(void); RG_API const char *rg_version(void);
RG_API void rg_config_init(rg_config *config); RG_API void rg_config_init(rg_config *config);
RG_API rg_context *rg_create(const rg_config *config); RG_API rg_context *rg_create(const rg_config *config);
RG_API void rg_destroy(rg_context *ctx); RG_API void rg_destroy(rg_context *ctx);
RG_API const char *rg_last_error(const rg_context *ctx); RG_API const char *rg_last_error(const rg_context *ctx);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
+122
View File
@@ -0,0 +1,122 @@
#include "internal.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *default_alloc(size_t size, void *user) {
(void)user;
return malloc(size);
}
static void *default_realloc(void *ptr, size_t old_size, size_t new_size,
void *user) {
(void)old_size;
(void)user;
return realloc(ptr, new_size);
}
static void default_free(void *ptr, size_t size, void *user) {
(void)size;
(void)user;
free(ptr);
}
static void default_log(int32_t level, const char *message, void *user) {
static const char *names[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR"};
(void)user;
fprintf(stderr, "[regolith %s] %s\n", names[level], message);
}
const char *rg_version(void) { return RG_VERSION_STRING; }
void rg_config_init(rg_config *config) {
memset(config, 0, sizeof *config);
config->struct_size = (uint32_t)sizeof *config;
config->allocator.alloc = default_alloc;
config->allocator.realloc = default_realloc;
config->allocator.free = default_free;
config->log = default_log;
config->log_level = RG_LOG_TRACE;
config->viewport_w = 1280;
config->viewport_h = 720;
config->display_scale = 1.0f;
}
void rg__log(rg_context *ctx, int32_t level, const char *fmt, ...) {
char buf[512];
va_list ap;
if (level < ctx->log_level)
return;
va_start(ap, fmt);
vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
ctx->log(level, buf, ctx->log_user);
}
int rg__fail(rg_context *ctx, int code, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(ctx->last_error, sizeof ctx->last_error, fmt, ap);
va_end(ap);
rg__log(ctx, RG_LOG_ERROR, "%s", ctx->last_error);
return code;
}
const char *rg_last_error(const rg_context *ctx) { return ctx->last_error; }
rg_context *rg_create(const rg_config *config) {
rg_config cfg;
rg_context *ctx;
size_t n;
rg_config_init(&cfg);
if (config) {
n = config->struct_size < sizeof cfg ? config->struct_size : sizeof cfg;
memcpy(&cfg, config, n);
}
if (!cfg.allocator.alloc || !cfg.allocator.realloc || !cfg.allocator.free) {
rg_config defaults;
rg_config_init(&defaults);
cfg.allocator = defaults.allocator;
}
if (!cfg.log)
cfg.log = default_log;
if (cfg.display_scale <= 0.0f)
cfg.display_scale = 1.0f;
ctx = cfg.allocator.alloc(sizeof *ctx, cfg.allocator.user);
if (!ctx)
return NULL;
memset(ctx, 0, sizeof *ctx);
ctx->alloc = cfg.allocator;
ctx->log = cfg.log;
ctx->log_user = cfg.log_user;
ctx->log_level = cfg.log_level;
ctx->viewport_w = cfg.viewport_w;
ctx->viewport_h = cfg.viewport_h;
ctx->display_scale = cfg.display_scale;
rg__log(ctx, RG_LOG_DEBUG, "clay minimum memory: %zu bytes",
rg__clay_min_memory());
rg__log(ctx, RG_LOG_INFO, "context created (%dx%d @ %.2fx)",
(int)ctx->viewport_w, (int)ctx->viewport_h,
(double)ctx->display_scale);
return ctx;
}
void rg_destroy(rg_context *ctx) {
rg_allocator a;
if (!ctx)
return;
rg__lua_close(ctx);
rg__log(ctx, RG_LOG_INFO, "context destroyed");
a = ctx->alloc;
a.free(ctx, sizeof *ctx, a.user);
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef RG_INTERNAL_H
#define RG_INTERNAL_H
#include "../include/regolith.h"
#if defined(__GNUC__)
#define RG_PRINTF(f, a) __attribute__((format(printf, f, a)))
#else
#define RG_PRINTF(f, a)
#endif
struct lua_State;
struct rg_context {
rg_allocator alloc;
rg_log_fn log;
void* log_user;
int32_t log_level;
int32_t viewport_w;
int32_t viewport_h;
float display_scale;
char last_error[256];
struct lua_State* L;
};
void rg__log(rg_context* ctx, int32_t level, const char* fmt, ...) RG_PRINTF(3, 4);
int rg__fail(rg_context* ctx, int code, const char* fmt, ...) RG_PRINTF(3, 4);
size_t rg__clay_min_memory(void);
void rg__lua_close(rg_context* ctx);
#endif
+10
View File
@@ -0,0 +1,10 @@
#define CLAY_IMPLEMENTATION
#include "../third_party/clay/clay.h"
#include "internal.h"
size_t rg__clay_min_memory(void)
{
return Clay_MinMemorySize();
}
+72
View File
@@ -0,0 +1,72 @@
#include "internal.h"
#include "../include/regolith_lua.h"
#include <string.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
static void *lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
rg_context *ctx = ud;
rg_allocator *a = &ctx->alloc;
if (nsize == 0) {
if (ptr) a->free(ptr, osize, a->user);
return NULL;
}
if (!ptr) return a->alloc(nsize, a->user);
return a->realloc(ptr, osize, nsize, a->user);
}
static int on_panic(lua_State *L)
{
void *ud;
const char *msg = lua_tostring(L, -1);
lua_getallocf(L, &ud);
rg__log(ud, RG_LOG_ERROR, "lua panic: %s", msg ? msg : "unknown");
return 0;
}
int rg_lua_open(rg_context *ctx)
{
lua_State *L;
if (ctx->L) return RG_OK;
L = lua_newstate(lua_alloc, ctx);
if (!L) return rg__fail(ctx, RG_ERR_NO_MEMORY, "could not create lua state");
lua_atpanic(L, on_panic);
luaL_openlibs(L);
ctx->L = L;
rg__log(ctx, RG_LOG_INFO, "lua opened (%s)", LUA_VERSION);
return RG_OK;
}
void rg__lua_close(rg_context *ctx)
{
if (!ctx->L) return;
lua_close(ctx->L);
ctx->L = NULL;
}
int rg_lua_dostring(rg_context *ctx, const char *code, const char *chunk_name)
{
lua_State *L = ctx->L;
if (!L) return rg__fail(ctx, RG_ERR_STATE, "lua is not open");
if (luaL_loadbuffer(L, code, strlen(code), chunk_name ? chunk_name : "=string") != LUA_OK ||
lua_pcall(L, 0, 0, 0) != LUA_OK) {
const char *msg = lua_tostring(L, -1);
int rc = rg__fail(ctx, RG_ERR_LUA, "%s", msg ? msg : "unknown lua error");
lua_pop(L, 1);
return rc;
}
return RG_OK;
}
struct lua_State *rg_lua_state(rg_context *ctx)
{
return ctx->L;
}
+6
View File
@@ -0,0 +1,6 @@
#define STB_RECT_PACK_IMPLEMENTATION
#include "../third_party/stb/stb_rect_pack.h"
#define STB_TRUETYPE_IMPLEMENTATION
#include "../third_party/stb/stb_truetype.h"
#include "internal.h"
+70
View File
@@ -0,0 +1,70 @@
#include "../include/regolith.h"
#include "../include/regolith_lua.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct counter {
size_t live;
size_t peak;
} counter;
static void track(counter *c, size_t add, size_t sub) {
c->live = c->live + add - sub;
if (c->live > c->peak)
c->peak = c->live;
}
static void *c_alloc(size_t size, void *user) {
void *p = malloc(size);
if (p)
track(user, size, 0);
return p;
}
static void *c_realloc(void *ptr, size_t old_size, size_t new_size,
void *user) {
void *p = realloc(ptr, new_size);
if (p)
track(user, new_size, old_size);
return p;
}
static void c_free(void *ptr, size_t size, void *user) {
track(user, 0, size);
free(ptr);
}
int main(void) {
counter c = {0, 0};
rg_config cfg;
rg_context *ctx;
int rc;
rg_config_init(&cfg);
cfg.allocator.alloc = c_alloc;
cfg.allocator.realloc = c_realloc;
cfg.allocator.free = c_free;
cfg.allocator.user = &c;
ctx = rg_create(&cfg);
if (!ctx)
return 1;
rc = rg_lua_open(ctx);
if (rc == RG_OK)
rc = rg_lua_dostring(ctx, "print('hello from ' .. _VERSION)", "=smoke");
if (rc == RG_OK)
rc = rg_lua_dostring(ctx, "this is not lua", "=smoke_bad") == RG_ERR_LUA
? RG_OK
: RG_ERR_GENERIC;
printf("regolith %s, peak %zu bytes\n", rg_version(), c.peak);
rg_destroy(ctx);
if (c.live != 0) {
printf("leak: %zu bytes still live\n", c.live);
return 1;
}
return rc == RG_OK ? 0 : 1;
}
+9
View File
@@ -0,0 +1,9 @@
#include <SDL3/SDL.h>
#include <stdio.h>
int main(void)
{
int v = SDL_GetVersion();
printf("SDL %d.%d.%d\n", SDL_VERSIONNUM_MAJOR(v), SDL_VERSIONNUM_MINOR(v), SDL_VERSIONNUM_MICRO(v));
return 0;
}