parent
2dcc1b582c
commit
39e78ea078
@ -1,41 +0,0 @@ |
||||
# linux kernel style formatting |
||||
BasedOnStyle: LLVM |
||||
IndentWidth: 8 |
||||
UseTab: AlignWithSpaces |
||||
|
||||
BreakBeforeBraces: Linux |
||||
AllowShortIfStatementsOnASingleLine: false |
||||
IndentCaseLabels: false |
||||
ColumnLimit: 85 |
||||
|
||||
InsertBraces: true |
||||
SortIncludes: Never |
||||
BinPackParameters: false |
||||
BinPackArguments: false |
||||
Cpp11BracedListStyle: true |
||||
SpaceBeforeCpp11BracedList: true |
||||
SeparateDefinitionBlocks: Always |
||||
AlignAfterOpenBracket: BlockIndent |
||||
InsertNewlineAtEOF: true |
||||
|
||||
AlignConsecutiveDeclarations: |
||||
Enabled: true |
||||
AcrossEmptyLines: false |
||||
AcrossComments: false |
||||
AlignCompound: true |
||||
PadOperators: false |
||||
|
||||
AlignConsecutiveMacros: |
||||
Enabled: true |
||||
AcrossEmptyLines: false |
||||
AcrossComments: true |
||||
|
||||
AlignConsecutiveBitFields: |
||||
Enabled: true |
||||
AcrossEmptyLines: false |
||||
AcrossComments: true |
||||
|
||||
AlignConsecutiveAssignments: |
||||
Enabled: true |
||||
AcrossEmptyLines: false |
||||
AcrossComments: true |
@ -1,8 +1,2 @@ |
||||
microgui |
||||
ugui |
||||
*.o |
||||
test/test |
||||
**/compile_commands.json |
||||
**/.cache |
||||
test |
||||
raylib/* |
||||
build/* |
||||
|
@ -0,0 +1,3 @@ |
||||
[submodule "lib/raylib.c3l"] |
||||
path = lib/raylib.c3l |
||||
url = https://github.com/NexushasTaken/raylib.c3l |
@ -1,15 +0,0 @@ |
||||
CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/include
|
||||
CC = gcc
|
||||
LDFLAGS = -Lraylib/lib -lm
|
||||
|
||||
all: ugui |
||||
|
||||
ugui: ugui.o vectree.o cache.o timer.o raylib/lib/libraylib.a |
||||
|
||||
ugui.o: ugui.c ugui.h |
||||
|
||||
vectree.o: vectree.c ugui.h |
||||
|
||||
cache.o: cache.c ugui.h |
||||
|
||||
timer.o: timer.c timer.h |
@ -1,211 +0,0 @@ |
||||
// LRU cache:
|
||||
/*
|
||||
* The cache uses a pool (array) containing all the elements and a hash table |
||||
* associating the position in the pool with the element id |
||||
*/ |
||||
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include "ugui.h" |
||||
|
||||
// To have less collisions TABLE_SIZE has to be larger than the cache size
|
||||
#define CACHE_SIZE 265 |
||||
#define TABLE_SIZE (CACHE_SIZE * 1.5f) |
||||
#define CACHE_NCYCLES (CACHE_SIZE * 2 / 3) |
||||
#define CACHE_BSIZE (((CACHE_SIZE + 0x3f) & (~0x3f)) >> 6) |
||||
#define CACHE_BRESET(b) \ |
||||
for (int i = 0; i < CACHE_BSIZE; b[i++] = 0) \
|
||||
; |
||||
#define CACHE_BSET(b, x) b[(x) >> 6] |= (uint64_t)1 << ((x)&63) |
||||
#define CACHE_BTEST(b, x) (b[(x) >> 6] & ((uint64_t)1 << ((x)&63))) |
||||
|
||||
/* Hash Table Implementation ----------------------------------------------------- */ |
||||
|
||||
#define HASH_MAXSIZE 4096 |
||||
|
||||
// hash table (id -> cache index)
|
||||
typedef struct { |
||||
UgId id; |
||||
uint32_t index; |
||||
} IdElem; |
||||
|
||||
typedef struct _IdTable { |
||||
uint32_t items, size, exp; |
||||
IdElem bucket[]; |
||||
} IdTable; |
||||
|
||||
IdTable *table_create(uint32_t size) |
||||
{ |
||||
if (!size || size > HASH_MAXSIZE) { |
||||
return NULL; |
||||
} |
||||
/* round to the greater power of two */ |
||||
/* FIXME: check for intger overflow here */ |
||||
uint32_t exp = 32 - __builtin_clz(size - 1); |
||||
size = 1 << (exp); |
||||
/* FIXME: check for intger overflow here */ |
||||
IdTable *ht = malloc(sizeof(IdTable) + sizeof(IdElem) * size); |
||||
if (ht) { |
||||
ht->items = 0; |
||||
ht->size = size; |
||||
memset(ht->bucket, 0, sizeof(IdTable) * size); |
||||
} |
||||
return ht; |
||||
} |
||||
|
||||
void table_destroy(IdTable *ht) |
||||
{ |
||||
if (ht) { |
||||
free(ht); |
||||
} |
||||
} |
||||
|
||||
// Find and return the element by pointer
|
||||
IdElem *table_search(IdTable *ht, UgId id) |
||||
{ |
||||
if (!ht) { |
||||
return NULL; |
||||
} |
||||
|
||||
// In this case id is the hash
|
||||
uint32_t idx = id % ht->size; |
||||
|
||||
for (uint32_t x = 0, i; x < ht->size; x++) { |
||||
i = (idx + x) % ht->size; |
||||
if (ht->bucket[i].id == 0 || ht->bucket[i].id == id) { |
||||
return &(ht->bucket[i]); |
||||
} |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
// FIXME: this simply overrides the found item
|
||||
IdElem *table_insert(IdTable *ht, IdElem entry) |
||||
{ |
||||
IdElem *r = table_search(ht, entry.id); |
||||
if (r != NULL) { |
||||
if (r->id != 0) { |
||||
ht->items++; |
||||
} |
||||
*r = entry; |
||||
} |
||||
return r; |
||||
} |
||||
|
||||
IdElem *table_remove(IdTable *ht, UgId id) |
||||
{ |
||||
if (!ht) { |
||||
return NULL; |
||||
} |
||||
IdElem *r = table_search(ht, id); |
||||
if (r) { |
||||
r->id = 0; |
||||
} |
||||
return r; |
||||
} |
||||
|
||||
/* Cache Implementation ---------------------------------------------------------- */ |
||||
|
||||
// Every CACHE_CYCLES operations mark not-present the unused elements
|
||||
#define CACHE_CYCLE(c) \ |
||||
do { \
|
||||
if (++(c->cycles) > CACHE_NCYCLES) { \
|
||||
for (int i = 0; i < CACHE_BSIZE; i++) { \
|
||||
c->present[i] &= c->used[i]; \
|
||||
c->used[i] = 0; \
|
||||
} \
|
||||
c->cycles = 0; \
|
||||
} \
|
||||
} while (0) |
||||
|
||||
/* FIXME: check for allocation errors */ |
||||
UgElemCache ug_cache_init(void) |
||||
{ |
||||
IdTable *t = table_create(TABLE_SIZE); |
||||
UgElem *a = malloc(sizeof(UgElem) * CACHE_SIZE); |
||||
uint64_t *p = malloc(sizeof(uint64_t) * CACHE_BSIZE); |
||||
uint64_t *u = malloc(sizeof(uint64_t) * CACHE_BSIZE); |
||||
CACHE_BRESET(p); |
||||
CACHE_BRESET(u); |
||||
return (UgElemCache) {.table = t, .array = a, .present = p, .used = u, 0}; |
||||
} |
||||
|
||||
void ug_cache_free(UgElemCache *cache) |
||||
{ |
||||
if (cache) { |
||||
table_destroy(cache->table); |
||||
free(cache->array); |
||||
free(cache->present); |
||||
free(cache->used); |
||||
} |
||||
} |
||||
|
||||
UgElem *ug_cache_search(UgElemCache *cache, UgId id) |
||||
{ |
||||
if (!cache) { |
||||
return NULL; |
||||
} |
||||
IdElem *r; |
||||
r = table_search(cache->table, id); |
||||
/* MISS */ |
||||
if (!r || id != r->id) { |
||||
return NULL; |
||||
} |
||||
/* MISS, the data is not valid (not present) */ |
||||
if (!CACHE_BTEST(cache->present, r->index)) { |
||||
return NULL; |
||||
} |
||||
/* HIT, set as recently used */ |
||||
CACHE_BSET(cache->used, r->index); |
||||
return (&cache->array[r->index]); |
||||
} |
||||
|
||||
/* Look for a free spot in the present bitmap and return its index */ |
||||
/* If there is no free space left then just return the first position */ |
||||
int ug_cache_get_free_spot(UgElemCache *cache) |
||||
{ |
||||
if (!cache) { |
||||
return -1; |
||||
} |
||||
int x = 0; |
||||
for (int b = 0; b < CACHE_BSIZE; b++) { |
||||
if (cache->present[b] == 0) { |
||||
x = 64; |
||||
} else { |
||||
x = __builtin_clzll(cache->present[b]); |
||||
} |
||||
x = 64 - x; |
||||
if (!CACHE_BTEST(cache->present, x + 64 * b)) { |
||||
return x + 64 * b; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) |
||||
{ |
||||
if (!cache) { |
||||
return NULL; |
||||
} |
||||
UgElem *spot = NULL; |
||||
|
||||
/* Set used and present */ |
||||
CACHE_BSET(cache->present, index); |
||||
CACHE_BSET(cache->used, index); |
||||
CACHE_CYCLE(cache); |
||||
spot = &(cache->array[index]); |
||||
*spot = *g; |
||||
IdElem e = {.id = g->id, .index = index}; |
||||
if (!table_insert(cache->table, e)) { |
||||
return NULL; |
||||
} |
||||
return spot; |
||||
} |
||||
|
||||
// Insert an element in the cache
|
||||
UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index) |
||||
{ |
||||
*index = ug_cache_get_free_spot(cache); |
||||
return ug_cache_insert_at(cache, g, *index); |
||||
} |
@ -1,5 +0,0 @@ |
||||
-Iraylib/include |
||||
-Wall |
||||
-Wextra |
||||
-pedantic |
||||
-std=c11 |
@ -1,193 +0,0 @@ |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <stdarg.h> |
||||
|
||||
#include <linux/limits.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/types.h> |
||||
|
||||
#define PATHS_NO 3 |
||||
|
||||
static char temp[PATH_MAX] = {0}; |
||||
static int valid_paths = 0; |
||||
static char paths[PATHS_NO][PATH_MAX] = {0}; // 0: xdg, 1: fallback, 2: global
|
||||
|
||||
static const mode_t CONFDIR_MODE = S_IRWXU | S_IRWXU | S_IRWXU | S_IRWXU; |
||||
|
||||
static void err(const char *fmt, ...) |
||||
{ |
||||
va_list ap; |
||||
va_start(ap, fmt); |
||||
|
||||
fprintf(stderr, "[libconf]: "); |
||||
vfprintf(stderr, fmt, ap); |
||||
fprintf(stderr, "\n"); |
||||
|
||||
va_end(ap); |
||||
} |
||||
|
||||
// implements "mkdir -p"
|
||||
static int mkdir_p(const char *path) |
||||
{ |
||||
char tmp_path[PATH_MAX] = {0}; |
||||
strncpy(tmp_path, path, PATH_MAX - 1); |
||||
struct stat st; |
||||
|
||||
for (char *tk = strtok(tmp_path, "/"); tk != NULL; tk = strtok(NULL, "/")) { |
||||
if (tk != tmp_path) { |
||||
tk[-1] = '/'; |
||||
} |
||||
|
||||
if (stat(tmp_path, &st)) { |
||||
if (errno != ENOENT) { |
||||
err("could not stat() %s: %s", |
||||
tmp_path, |
||||
strerror(errno)); |
||||
return 1; |
||||
} |
||||
|
||||
if (mkdir(tmp_path, CONFDIR_MODE)) { |
||||
err("could not mkdir() %s: %s", |
||||
tmp_path, |
||||
strerror(errno)); |
||||
return 1; |
||||
} |
||||
} else if (!S_ISDIR(st.st_mode)) { |
||||
err("could not create directory %s: file exists but it is " |
||||
"not a directory", |
||||
tmp_path); |
||||
return 1; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
// TODO: add a way to add a custom directory to the paths, for example via an
|
||||
// environment variable
|
||||
// TODO: maybe add a LIBCONF_PATH that overrides the defaults
|
||||
/* default paths are:
|
||||
* 1. ${XDG_CONFIG_HOME}/libconf/appid.d/ |
||||
* 2. ${HOME}/.config/libconf/appid.d/ |
||||
* 3. etc/libconf/appid.d/ |
||||
*/ |
||||
static int fill_paths(const char *id) |
||||
{ |
||||
// TODO: verify id
|
||||
if (id == NULL) { |
||||
err("must provide a valid app id"); |
||||
return 1; |
||||
} |
||||
|
||||
const char *xdg = getenv("XDG_CONFIG_HOME"); |
||||
if (xdg != NULL) { |
||||
snprintf(paths[0], PATH_MAX, "%s/libconf/%s.d", xdg, id); |
||||
} |
||||
|
||||
const char *home = getenv("HOME"); |
||||
if (home) { |
||||
snprintf(temp, PATH_MAX, "%s/.config/libconf/%s.d", home, id); |
||||
|
||||
if (mkdir_p(temp) == 0) { |
||||
strcpy(paths[1], temp); |
||||
} |
||||
} |
||||
|
||||
// do not create global config path since most likely we don't have
|
||||
// the necessary permissions to do so
|
||||
snprintf(paths[2], PATH_MAX, "/etc/libconf/%s.d", id); |
||||
|
||||
for (size_t i = 0; i < PATHS_NO; i++) { |
||||
printf("paths[%ld] = %s\n", i, paths[i]); |
||||
} |
||||
|
||||
valid_paths = 1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
// get config file path
|
||||
const char *lcnf_get_conf_file(const char *id, const char *name, int global) |
||||
{ |
||||
static char str[PATH_MAX] = {0}; |
||||
|
||||
if (id == NULL) { |
||||
return NULL; |
||||
} |
||||
|
||||
if (!valid_paths) { |
||||
if (fill_paths(id)) { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
// quick path for global config file
|
||||
if (global && paths[2][0] != '\0') { |
||||
snprintf(str, PATH_MAX, "%s/%s", paths[2], name); |
||||
return str; |
||||
} |
||||
|
||||
int found = 0; |
||||
for (size_t i = 0; i < PATHS_NO; i++) { |
||||
if (paths[i][0] == '\0') { |
||||
continue; |
||||
} |
||||
|
||||
struct stat st; |
||||
snprintf(str, PATH_MAX, "%s/%s", paths[i], name); |
||||
|
||||
if (stat(str, &st) && errno != ENOENT) { |
||||
err("could not stat %s: %s", str, strerror(errno)); |
||||
} |
||||
|
||||
if (S_ISREG(st.st_mode)) { |
||||
found = 1; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return found ? str : NULL; |
||||
} |
||||
|
||||
// get config file directory path
|
||||
const char *lcnf_get_conf_dir(const char *id, int global) |
||||
{ |
||||
if (id == NULL) { |
||||
return NULL; |
||||
} |
||||
|
||||
if (global) { |
||||
return paths[2][0] != '\0' ? paths[2] : NULL; |
||||
} |
||||
|
||||
if (paths[0][0] != '\0') { |
||||
return paths[0]; |
||||
} else if (paths[1][0] != '\0') { |
||||
return paths[1]; |
||||
} else { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
// TODO: watch directory for file updates
|
||||
int lcnf_watch_conf_dir(const char *id, int global); |
||||
|
||||
// TODO: collect all files into a temporary one, useful for when you have multiple
|
||||
// configuration files like 00-foo.conf, 01-bar.conf and 99-baz.conf
|
||||
const char *lcnf_collect_conf_files(const char *id, int global); |
||||
|
||||
#if 1 |
||||
|
||||
int main(void) |
||||
{ |
||||
const char *p = lcnf_get_conf_file("pippo", "pippo.toml", 0); |
||||
if (p) { |
||||
printf("config found: %s\n", p); |
||||
} else { |
||||
printf("config not found\n"); |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
#endif |
@ -1,7 +0,0 @@ |
||||
#ifndef LIBCONF_H_ |
||||
#define LIBCONF_H_ |
||||
|
||||
const char *lcnf_get_conf_file(const char *id, const char *name, int global); |
||||
const char *lcnf_get_conf_dir(const char *id, int global); |
||||
|
||||
#endif |
@ -1,8 +0,0 @@ |
||||
#!/bin/sh |
||||
|
||||
mkdir -p raylib |
||||
wget https://github.com/raysan5/raylib/releases/download/5.0/raylib-5.0_linux_amd64.tar.gz |
||||
tar -xvf raylib-5.0_linux_amd64.tar.gz |
||||
mv ./raylib-5.0_linux_amd64/* ./raylib/ |
||||
|
||||
rm -rf raylib-5.0_linux_amd64 raylib-5.0_linux_amd64.tar.gz |
@ -0,0 +1 @@ |
||||
Subproject commit c7ebe054ce16136c1128fab54fcce4921044293e |
@ -0,0 +1,56 @@ |
||||
{ |
||||
// Language version of C3. |
||||
"langrev": "1", |
||||
// Warnings used for all targets. |
||||
"warnings": [ "no-unused" ], |
||||
// Directories where C3 library files may be found. |
||||
"dependency-search-paths": [ "lib" ], |
||||
// Libraries to use for all targets. |
||||
"dependencies": [ "raylib" ], |
||||
"features": [ |
||||
// See rcore.c3 |
||||
//"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", |
||||
//"SUPPORT_STANDARD_FILEIO", |
||||
//"SUPPORT_FILE_SYSTEM_FUNCTIONS", |
||||
//"SUPPORT_DATA_ENCODER", |
||||
// See text.c3 |
||||
//"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", |
||||
//"SUPPORT_TEXT_C_STRING_MANAGEMENT", |
||||
//"SUPPORT_RANDOM_GENERATION", |
||||
|
||||
"SUPPORT_RAYGUI", |
||||
//"RAYGUI_NO_ICONS", |
||||
//"RAYGUI_CUSTOM_ICONS", |
||||
], |
||||
// Authors, optionally with email. |
||||
"authors": [ "John Doe <john.doe@example.com>" ], |
||||
// Version using semantic versioning. |
||||
"version": "0.1.0", |
||||
// Sources compiled for all targets. |
||||
"sources": [ "src/**" ], |
||||
// C sources if the project also compiles C sources |
||||
// relative to the project file. |
||||
// "c-sources": [ "csource/**" ], |
||||
// Include directories for C sources relative to the project file. |
||||
// "c-include-dirs": [ "csource/include" ], |
||||
// Output location, relative to project file. |
||||
"output": "build", |
||||
// Architecture and OS target. |
||||
// You can use 'c3c --list-targets' to list all valid targets. |
||||
// "target": "windows-x64", |
||||
// Targets. |
||||
"targets": { |
||||
"ugui": { |
||||
// Executable or library. |
||||
"type": "executable", |
||||
// Additional libraries, sources |
||||
// and overrides of global settings here. |
||||
}, |
||||
}, |
||||
// Global settings. |
||||
// CPU name, used for optimizations in the LLVM backend. |
||||
"cpu": "generic", |
||||
// Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". |
||||
"opt": "O0", |
||||
// See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. |
||||
} |
@ -0,0 +1,133 @@ |
||||
module cache(<Key, Value, SIZE>); |
||||
|
||||
/* LRU Cache |
||||
* The cache uses a pool (array) to store all the elements, each element has |
||||
* a key (id) and a value. A HashMap correlates the ids to an index in the pool. |
||||
* To keep track of which items were recently used two bit arrays are kept, one |
||||
* stores the "used" flag for each index and anothe the "present" flag. |
||||
* Every NCYCLES operations the present and used arrays are updated to free up |
||||
* the elements that were not recently used. |
||||
*/ |
||||
|
||||
// FIXME: this module should really allocate all resources on an arena or temp |
||||
// allocator, since all memory allocations are connected and freeing |
||||
// happens at the same time |
||||
|
||||
import std::core::mem; |
||||
import std::collections::bitset; |
||||
import std::collections::map; |
||||
|
||||
def BitArr = bitset::BitSet(<SIZE>) @private; |
||||
def IdTable = map::Map(<Key, usz>) @private; |
||||
def IdTableEntry = map::Entry(<Key, usz>) @private; |
||||
|
||||
const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); |
||||
|
||||
struct Cache { |
||||
BitArr present, used; |
||||
IdTable table; |
||||
Value[] pool; |
||||
usz cycle_count; |
||||
} |
||||
|
||||
// Every CACHE_CYCLES operations mark as not-present the unused elements |
||||
macro Cache.cycle(&cache) @private { |
||||
cache.cycle_count++; |
||||
if (cache.cycle_count > CACHE_NCYCLES) { |
||||
for (usz i = 0; i < cache.present.data.len; i++) { |
||||
cache.present.data[i] &= cache.used.data[i]; |
||||
cache.used.data[i] = 0; |
||||
} |
||||
cache.cycle_count = 0; |
||||
} |
||||
} |
||||
|
||||
fn void! Cache.init(&cache) |
||||
{ |
||||
cache.table = map::new(<Key, usz>)(SIZE); |
||||
// FIXME: this shit is SLOW |
||||
foreach (idx, bit : cache.used) { cache.used[idx] = false; } |
||||
foreach (idx, bit : cache.present) { cache.present[idx] = false; } |
||||
cache.pool = mem::new_array(Value, SIZE); |
||||
} |
||||
|
||||
fn void Cache.free(&cache) |
||||
{ |
||||
(void)cache.table.free(); |
||||
(void)mem::free(cache.pool); |
||||
} |
||||
|
||||
fn Value*! Cache.search(&cache, Key id) |
||||
{ |
||||
// get_entry() faults on miss |
||||
IdTableEntry* entry = cache.table.get_entry(id)!; |
||||
|
||||
/* MISS */ |
||||
if (entry.key != id) { |
||||
return SearchResult.MISSING?; |
||||
} |
||||
|
||||
/* MISS, the data is not valid (not present) */ |
||||
if (!cache.present[entry.value]) { |
||||
// if the data is not present but it is still in the table, remove it |
||||
cache.table.remove(id)!; |
||||
return SearchResult.MISSING?; |
||||
} |
||||
|
||||
/* HIT, set as recently used */ |
||||
cache.used[entry.value] = true; |
||||
return &(cache.pool[entry.value]); |
||||
} |
||||
|
||||
/* Look for a free spot in the present bitmap and return its index */ |
||||
/* If there is no free space left then just return the first position */ |
||||
fn usz Cache.get_free_spot(&cache) @private |
||||
{ |
||||
// FIXME: This shit is SLOW, especially when clz() exists |
||||
foreach (idx, bit: cache.present) { |
||||
if (bit == false) { |
||||
return idx; |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private |
||||
{ |
||||
// TODO: verify index, g and id |
||||
Value* spot; |
||||
|
||||
/* Set used and present */ |
||||
cache.present.set(index); |
||||
cache.used.set(index); |
||||
cache.cycle(); |
||||
|
||||
spot = &(cache.pool[index]); |
||||
*spot = *g; |
||||
cache.table.set(id, index); |
||||
return spot; |
||||
} |
||||
|
||||
// Insert an element in the cache, returns the index |
||||
fn Value*! Cache.insert_new(&cache, Value* g, Key id) |
||||
{ |
||||
usz index = cache.get_free_spot(); |
||||
return cache.insert_at(g, id, index); |
||||
} |
||||
|
||||
fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) |
||||
{ |
||||
Value*! c = cache.search(id); |
||||
if (catch e = c) { |
||||
if (e != SearchResult.MISSING) { |
||||
return e?; |
||||
} else { |
||||
// if the element is new (inserted) set the is_new flag |
||||
if (is_new) *is_new = true; |
||||
return cache.insert_new(g, id); |
||||
} |
||||
} else { |
||||
if (is_new) *is_new = false; |
||||
return c; |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
module fifo(<Type>); |
||||
|
||||
import std::core::mem; |
||||
|
||||
fault FifoErr { |
||||
FULL, |
||||
EMPTY, |
||||
} |
||||
|
||||
// TODO: specify the allocator |
||||
|
||||
struct Fifo { |
||||
Type[] arr; |
||||
usz out; |
||||
usz count; |
||||
} |
||||
|
||||
fn void! Fifo.init(&fifo, usz size) |
||||
{ |
||||
fifo.arr = mem::new_array(Type, size); |
||||
fifo.out = 0; |
||||
fifo.count = 0; |
||||
} |
||||
|
||||
fn void Fifo.free(&fifo) |
||||
{ |
||||
(void)mem::free(fifo.arr); |
||||
} |
||||
|
||||
fn void! Fifo.enqueue(&fifo, Type *elem) |
||||
{ |
||||
if (fifo.count >= fifo.arr.len) { |
||||
return FifoErr.FULL?; |
||||
} |
||||
usz in = (fifo.out + fifo.count) % fifo.arr.len; |
||||
fifo.arr[in] = *elem; |
||||
fifo.count++; |
||||
} |
||||
|
||||
fn Type*! Fifo.dequeue(&fifo) |
||||
{ |
||||
if (fifo.count == 0) { |
||||
return FifoErr.EMPTY?; |
||||
} |
||||
Type *ret = &fifo.arr[fifo.out]; |
||||
fifo.count--; |
||||
fifo.out = (fifo.out + 1) % fifo.arr.len; |
||||
return ret; |
||||
} |
@ -0,0 +1,196 @@ |
||||
import std::io; |
||||
import vtree; |
||||
import cache; |
||||
import ugui; |
||||
import rl; |
||||
|
||||
fn int main(String[] args) |
||||
{ |
||||
ugui::Ctx ctx; |
||||
ctx.init()!!; |
||||
|
||||
short width = 800; |
||||
short height = 450; |
||||
rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); |
||||
rl::init_window(width, height, "Ugui Test"); |
||||
ctx.input_window_size(width, height)!!; |
||||
|
||||
double[10][4] median_times; |
||||
isz frame; |
||||
double median_input, median_layout, median_draw, median_tot; |
||||
|
||||
// Main loop |
||||
while (!rl::window_should_close()) { |
||||
const int PARTIAL_INPUT = 0; |
||||
const int PARTIAL_LAYOUT = 1; |
||||
const int PARTIAL_DRAW = 2; |
||||
//timer_start(); |
||||
|
||||
/*** Start Input Handling ***/ |
||||
if (rl::is_window_resized()) { |
||||
width = (short)rl::get_screen_width(); |
||||
height = (short)rl::get_screen_height(); |
||||
ctx.input_window_size(width, height)!!; |
||||
} |
||||
|
||||
ctx.input_changefocus(rl::is_window_focused()); |
||||
|
||||
// FIXME: In raylib it doesn't seem to be a quick way to check if |
||||
// a mouse input event was received, so for now just use |
||||
// the delta information |
||||
rl::Vector2 mousedelta = rl::get_mouse_delta(); |
||||
if (mousedelta.x || mousedelta.y) { |
||||
ctx.input_mouse_delta((short)mousedelta.x, (short)mousedelta.y); |
||||
} |
||||
|
||||
ugui::MouseButtons buttons; |
||||
buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT); |
||||
buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); |
||||
buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); |
||||
ctx.input_mouse_button(buttons); |
||||
|
||||
//timer_partial(PARTIAL_INPUT); |
||||
/*** End Input Handling ***/ |
||||
|
||||
/*** Start UI Handling ***/ |
||||
ctx.frame_begin()!!; |
||||
|
||||
// main div, fill the whole window |
||||
ctx.div_begin("main", ugui::DIV_FILL)!!; |
||||
{| |
||||
ctx.layout_set_column()!!; |
||||
if (ctx.button("button0", ugui::Rect{.y = 100, .x = 100, .w = 30, .h = 30})!!.mouse_hold) { |
||||
io::printn("HOLDING button0"); |
||||
} |
||||
ctx.layout_next_column()!!; |
||||
ctx.button("button1", ugui::Rect{.w = 30, .h = 30})!!; |
||||
ctx.layout_next_column()!!; |
||||
ctx.button("button2", ugui::Rect{.w = 30, .h = 30})!!; |
||||
|}; |
||||
ctx.div_end()!!; |
||||
|
||||
ctx.frame_end()!!; |
||||
//timer_partial(PARTIAL_LAYOUT); |
||||
/*** End UI Handling ***/ |
||||
|
||||
/*** Start UI Drawing ***/ |
||||
rl::begin_drawing(); |
||||
// ClearBackground(BLACK); |
||||
|
||||
io::printn("----- Draw Begin -----"); |
||||
rl::Color c; |
||||
for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { |
||||
switch (cmd.type) { |
||||
case ugui::CmdType.CMD_RECT: |
||||
io::printfn( |
||||
"draw rect x=%d y=%d w=%d h=%d", |
||||
cmd.rect.rect.x, |
||||
cmd.rect.rect.y, |
||||
cmd.rect.rect.w, |
||||
cmd.rect.rect.h |
||||
); |
||||
c = rl::Color{ |
||||
.r = cmd.rect.color.r, |
||||
.g = cmd.rect.color.g, |
||||
.b = cmd.rect.color.b, |
||||
.a = cmd.rect.color.a, |
||||
}; |
||||
rl::draw_rectangle( |
||||
cmd.rect.rect.x, |
||||
cmd.rect.rect.y, |
||||
cmd.rect.rect.w, |
||||
cmd.rect.rect.h, |
||||
c |
||||
); |
||||
default: |
||||
io::printfn("Unknown cmd type: %d", cmd.type); |
||||
} |
||||
} |
||||
io::printf("----- Draw End -----\n\n"); |
||||
|
||||
rl::end_drawing(); |
||||
//timer_partial(PARTIAL_DRAW); |
||||
//timer_stop(); |
||||
/*** End UI Drawing ***/ |
||||
/* |
||||
median_times[frame][PARTIAL_INPUT] = |
||||
1e3 * timer_get_sec(PARTIAL_INPUT); |
||||
median_times[frame][PARTIAL_LAYOUT] = |
||||
1e3 * timer_get_sec(PARTIAL_LAYOUT); |
||||
median_times[frame][PARTIAL_DRAW] = |
||||
1e3 * timer_get_sec(PARTIAL_DRAW); |
||||
median_times[frame][3] = 1e3 * timer_get_sec(-1); |
||||
*/ |
||||
frame += 1; |
||||
frame %= 10; |
||||
/* |
||||
if (frame == 0) { |
||||
median_input = 0; |
||||
median_layout = 0; |
||||
median_draw = 0; |
||||
median_tot = 0; |
||||
for (size_t i = 0; i < 10; i++) { |
||||
median_input += median_times[i][PARTIAL_INPUT]; |
||||
median_layout += median_times[i][PARTIAL_LAYOUT]; |
||||
median_draw += median_times[i][PARTIAL_DRAW]; |
||||
median_tot += median_times[i][3]; |
||||
} |
||||
median_input /= 10; |
||||
median_layout /= 10; |
||||
median_draw /= 10; |
||||
median_tot /= 10; |
||||
} |
||||
|
||||
printf("input time: %lfms\n", median_input); |
||||
printf("layout time: %lfms\n", median_layout); |
||||
printf("draw time: %lfms\n", median_draw); |
||||
printf("total time: %lfms\n", median_tot); |
||||
|
||||
// Throttle Frames |
||||
// TODO: add an fps limit, time frame generation and log it |
||||
const float TARGET_FPS = 100; |
||||
float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); |
||||
WaitTime(wait_time); |
||||
*/ |
||||
} |
||||
|
||||
rl::close_window(); |
||||
|
||||
ctx.free(); |
||||
return 0; |
||||
} |
||||
|
||||
fn void! test_vtree() @test |
||||
{ |
||||
vtree::VTree(<String>) vt; |
||||
vt.init(10)!!; |
||||
defer vt.free(); |
||||
|
||||
assert(vt.size() == 10, "Size is incorrect"); |
||||
|
||||
isz ref = vt.add("Ciao Mamma", 0)!!; |
||||
String s = vt.get(ref)!!; |
||||
assert(s == "Ciao Mamma", "String is incorrect"); |
||||
isz par = vt.parentof(0)!!; |
||||
assert(ref == par, "Not Root"); |
||||
|
||||
vt.print(); |
||||
} |
||||
|
||||
def StrCache = cache::Cache(<int, String, 256>); |
||||
fn void! test_cache() @test |
||||
{ |
||||
StrCache cc; |
||||
cc.init()!!; |
||||
defer cc.free(); |
||||
|
||||
String*! r = cc.search(1); |
||||
if (catch ex = r) { |
||||
if (ex != SearchResult.MISSING) { |
||||
return ex?; |
||||
} |
||||
} |
||||
|
||||
r = cc.get_or_insert(&&"Ciao Mamma", 1)!; |
||||
assert(*r!! == "Ciao Mamma", "incorrect string"); |
||||
} |
@ -0,0 +1,184 @@ |
||||
module ugui; |
||||
|
||||
import vtree; |
||||
import cache; |
||||
import fifo; |
||||
|
||||
struct Rect { |
||||
short x, y, w, h; |
||||
} |
||||
|
||||
struct Point { |
||||
short x, y; |
||||
} |
||||
|
||||
struct Color{ |
||||
char r, g, b, a; |
||||
} |
||||
|
||||
// element ids are just long ints |
||||
def Id = usz; |
||||
|
||||
enum ElemType { |
||||
ETYPE_NONE, |
||||
ETYPE_DIV, |
||||
ETYPE_BUTTON, |
||||
} |
||||
|
||||
bitstruct ElemFlags : uint { |
||||
bool updated : 0; |
||||
bool has_focus : 1; |
||||
} |
||||
|
||||
bitstruct ElemEvents : uint { |
||||
bool key_press : 0; |
||||
bool key_release : 1; |
||||
bool key_hold : 2; |
||||
bool mouse_hover : 3; |
||||
bool mouse_press : 4; |
||||
bool mouse_release : 5; |
||||
bool mouse_hold : 6; |
||||
} |
||||
|
||||
enum DivLayout { |
||||
LAYOUT_ROW, |
||||
LAYOUT_COLUMN, |
||||
LAYOUT_FLOATING, |
||||
} |
||||
|
||||
// div element |
||||
struct Div { |
||||
DivLayout layout; |
||||
Point origin_r, origin_c; |
||||
Color color_bg; |
||||
} |
||||
|
||||
// element structure |
||||
struct Elem { |
||||
Id id; |
||||
ElemFlags flags; |
||||
ElemEvents events; |
||||
Rect rect; |
||||
ElemType type; |
||||
union { |
||||
Div div; |
||||
} |
||||
} |
||||
|
||||
|
||||
// relationships between elements are stored in a tree, it stores just the ids |
||||
def IdTree = vtree::VTree(<Id>) @private; |
||||
|
||||
// elements themselves are kept in a cache |
||||
const uint MAX_ELEMENTS = 2048; |
||||
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private; |
||||
|
||||
def CmdQueue = fifo::Fifo(<Cmd>); |
||||
|
||||
fault UgError { |
||||
INVALID_SIZE, |
||||
EVENT_UNSUPPORTED, |
||||
UNEXPECTED_ELEMENT, |
||||
} |
||||
|
||||
fn Id fnv1a(String str) |
||||
{ |
||||
const ulong FNV_OFF = 0xcbf29ce484222325; |
||||
const ulong FNV_PRIME = 0x100000001b3; |
||||
|
||||
ulong hash = FNV_OFF; |
||||
foreach (c : str) { |
||||
hash ^= c; |
||||
hash *= FNV_PRIME; |
||||
} |
||||
|
||||
return hash; |
||||
} |
||||
|
||||
macro hash(String str) { return fnv1a(str); } |
||||
|
||||
macro uint_to_rgba(uint u) { |
||||
return Color{ |
||||
.r = (char)((u >> 24) & 0xff), |
||||
.g = (char)((u >> 16) & 0xff), |
||||
.b = (char)((u >> 8) & 0xff), |
||||
.a = (char)((u >> 0) & 0xff) |
||||
}; |
||||
} |
||||
|
||||
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; |
||||
|
||||
macro abs(a) { return a < 0 ? -a : a; } |
||||
macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); } |
||||
|
||||
const uint STACK_STEP = 10; |
||||
const uint MAX_ELEMS = 128; |
||||
const uint MAX_CMDS = 256; |
||||
const uint ROOT_ID = 1; |
||||
|
||||
// command type |
||||
enum CmdType { |
||||
CMD_RECT, |
||||
} |
||||
|
||||
// command to draw a rect |
||||
struct CmdRect { |
||||
Rect rect; |
||||
Color color; |
||||
} |
||||
|
||||
// command structure |
||||
struct Cmd { |
||||
CmdType type; |
||||
union { |
||||
CmdRect rect; |
||||
} |
||||
} |
||||
|
||||
enum Layout { |
||||
ROW, |
||||
COLUMN, |
||||
FLOATING |
||||
} |
||||
|
||||
// global style, similar to the css box model |
||||
struct Style { // css box model |
||||
Rect padding; |
||||
Rect border; |
||||
Rect margin; |
||||
Color bgcolor; // background color |
||||
Color fgcolor; // foreground color |
||||
Color bcolor; // border color |
||||
} |
||||
|
||||
|
||||
struct Ctx { |
||||
Layout layout; |
||||
IdTree tree; |
||||
ElemCache cache; |
||||
CmdQueue cmd_queue; |
||||
// total size in pixels of the context |
||||
ushort width, height; |
||||
Style style; |
||||
|
||||
bool has_focus; |
||||
struct input { |
||||
InputEvents events; |
||||
struct mouse { |
||||
Point pos, delta; |
||||
// mouse_down: bitmap of mouse buttons that are held |
||||
// mouse_updated: bitmap of mouse buttons that have been updated |
||||
// mouse_released = mouse_updated & ~mouse_down |
||||
// mouse_pressed = mouse_updated & mouse_down |
||||
MouseButtons down; |
||||
MouseButtons updated; |
||||
} |
||||
} |
||||
|
||||
isz active_div; // tree node indicating the current active div |
||||
} |
||||
|
||||
macro point_in_rect(Point p, Rect r) |
||||
{ |
||||
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); |
||||
} |
@ -0,0 +1,244 @@ |
||||
module ugui; |
||||
|
||||
import std::io; |
||||
|
||||
// return a pointer to the parent of the current active div |
||||
fn Elem*! Ctx.get_parent(&ctx) |
||||
{ |
||||
// FIXME: if the tree held pointers to the elements then no more |
||||
// redundant cache search |
||||
Id parent_id = ctx.tree.get(ctx.active_div)!; |
||||
return ctx.cache.search(parent_id); |
||||
} |
||||
|
||||
fn void! Ctx.init(&ctx) |
||||
{ |
||||
ctx.tree.init(MAX_ELEMENTS)!; |
||||
defer catch { (void)ctx.tree.free(); } |
||||
//ug_fifo_init(&ctx.fifo, MAX_CMDS); |
||||
|
||||
ctx.cache.init()!; |
||||
defer catch { (void)ctx.cache.free(); } |
||||
|
||||
ctx.cmd_queue.init(MAX_ELEMENTS)!; |
||||
defer catch { (void)ctx.cmd_queue.free(); } |
||||
|
||||
ctx.layout = Layout.ROW; |
||||
ctx.active_div = 0; |
||||
|
||||
// TODO: add style config |
||||
ctx.style.margin = Rect{1, 1, 1, 1}; |
||||
} |
||||
|
||||
fn void Ctx.free(&ctx) |
||||
{ |
||||
(void)ctx.tree.free(); |
||||
(void)ctx.cache.free(); |
||||
(void)ctx.cmd_queue.free(); |
||||
} |
||||
|
||||
fn void! Ctx.frame_begin(&ctx) |
||||
{ |
||||
// 1. Create the root div element |
||||
// NOTE: in c3 everythong is zero initialized by default |
||||
Rect space = { |
||||
.w = ctx.width, |
||||
.h = ctx.height, |
||||
}; |
||||
|
||||
Elem root = { |
||||
.id = ROOT_ID, |
||||
.type = ETYPE_DIV, |
||||
.rect = space, |
||||
.div = { |
||||
.layout = LAYOUT_ROW, |
||||
}, |
||||
}; |
||||
|
||||
// The root should have the updated flag only if the size of the window |
||||
// was changed between frames, this propagates an element size recalculation |
||||
// down the element tree |
||||
if (ctx.input.events.resize) { |
||||
root.flags.updated = true; |
||||
} |
||||
|
||||
// if the window has focus then the root element also has focus, no other |
||||
// computation needed, child elements need to check the mouse positon and |
||||
// other stuff |
||||
if (ctx.has_focus) { |
||||
root.flags.has_focus = true; |
||||
} |
||||
|
||||
// FIXME: check errors |
||||
// 2. Get the root element from the cache and update it |
||||
bool is_new; |
||||
Elem empty_elem; |
||||
Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, root.id, &is_new)!; |
||||
// flags always need to be set to the new flags |
||||
c_elem.flags = root.flags; |
||||
if (is_new || root.flags.updated) { |
||||
*c_elem = root; |
||||
} |
||||
|
||||
// 3. Push the root element into the element tree |
||||
ctx.active_div = ctx.tree.add(root.id, 0)!; |
||||
|
||||
// print_tree(ctx); |
||||
|
||||
// The root element does not push anything to the stack |
||||
// TODO: add a background color taken from a theme or config |
||||
|
||||
io::printn("##### Frame Begin #####"); |
||||
} |
||||
|
||||
fn void! Ctx.frame_end(&ctx) |
||||
{ |
||||
// 1. clear the tree |
||||
ctx.tree.prune(0)!; |
||||
|
||||
// 2. clear input fields |
||||
ctx.input.events = (InputEvents)0; |
||||
|
||||
// draw mouse position |
||||
$if 1: |
||||
Cmd cmd = { |
||||
.type = CMD_RECT, |
||||
.rect.rect = { |
||||
.x = ctx.input.mouse.pos.x - 2, |
||||
.y = ctx.input.mouse.pos.y - 2, |
||||
.w = 4, |
||||
.h = 4, |
||||
}, |
||||
.rect.color = uint_to_rgba(0xff00ffff) |
||||
}; |
||||
ctx.cmd_queue.enqueue(&cmd)!; |
||||
$endif |
||||
|
||||
io::printn("##### Frame End #####"); |
||||
} |
||||
|
||||
fn void! Ctx.div_begin(&ctx, String label, Rect size) |
||||
{ |
||||
Id id = hash(label); |
||||
|
||||
bool is_new; |
||||
Elem empty_elem; |
||||
Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; |
||||
|
||||
// FIXME: why save the id in the tree and not something more direct like |
||||
// the element pointer or the index into the cache vector? |
||||
isz div_node = ctx.tree.add(id, ctx.active_div)!; |
||||
|
||||
Elem *parent = ctx.get_parent()!; |
||||
|
||||
ctx.tree.print(); |
||||
|
||||
// Use the current div |
||||
ctx.active_div = div_node; |
||||
|
||||
// 1. Fill the element fields |
||||
// this resets the flags |
||||
c_elem.type = ETYPE_DIV; |
||||
c_elem.flags = (ElemFlags)0; |
||||
|
||||
// do layout and update flags only if the element was updated |
||||
if (is_new || parent.flags.updated) { |
||||
// 2. layout the element |
||||
c_elem.rect = ctx.position_element(parent, size); |
||||
|
||||
// 3. Mark the element as updated |
||||
c_elem.flags.updated = true; |
||||
// 4. Fill the div fields |
||||
c_elem.div.layout = parent.div.layout; |
||||
c_elem.div.origin_c = Point{ |
||||
.x = c_elem.rect.x, |
||||
.y = c_elem.rect.y, |
||||
}; |
||||
c_elem.div.color_bg = uint_to_rgba(0xff0000ff); |
||||
c_elem.div.origin_r = c_elem.div.origin_c; |
||||
} else if (parent.flags.has_focus) { |
||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.rect)) { |
||||
c_elem.flags.has_focus = true; |
||||
} |
||||
} else { |
||||
// TODO: check active |
||||
// TODO: check scrollbars |
||||
// TODO: check resizeable |
||||
} |
||||
|
||||
// Add the background to the draw stack |
||||
Cmd cmd = { |
||||
.type = CMD_RECT, |
||||
.rect = { |
||||
.rect = c_elem.rect, |
||||
.color = c_elem.div.color_bg, |
||||
}, |
||||
}; |
||||
ctx.cmd_queue.enqueue(&cmd)!; |
||||
} |
||||
|
||||
fn void! Ctx.div_end(&ctx) |
||||
{ |
||||
// the active_div returns to the parent of the current one |
||||
ctx.active_div = ctx.tree.parentof(ctx.active_div)!; |
||||
} |
||||
|
||||
// @ensure elem != null |
||||
fn bool Ctx.is_hovered(&ctx, Elem *elem) |
||||
{ |
||||
return point_in_rect(ctx.input.mouse.pos, elem.rect); |
||||
} |
||||
|
||||
fn ElemEvents! Ctx.button(&ctx, String label, Rect size) |
||||
{ |
||||
Id id = hash(label); |
||||
|
||||
// TODO: do layouting if the element is new or the parent has updated |
||||
bool is_new; |
||||
Elem empty_elem; |
||||
Elem *c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; |
||||
|
||||
// add it to the tree |
||||
ctx.tree.add(id, ctx.active_div)!; |
||||
ctx.tree.print(); |
||||
|
||||
Elem *parent = ctx.get_parent()!; |
||||
|
||||
// 1. Fill the element fields |
||||
// this resets the flags |
||||
c_elem.type = ETYPE_BUTTON; |
||||
c_elem.flags = (ElemFlags)0; |
||||
Color bg_color = uint_to_rgba(0x0000ffff); |
||||
|
||||
// if the element is new or the parent was updated then redo layout |
||||
if (is_new || parent.flags.updated) { |
||||
// 2. Layout |
||||
c_elem.rect = ctx.position_element(parent, size, true); |
||||
|
||||
// TODO: 3. Fill the button specific fields |
||||
} |
||||
|
||||
// TODO: Check for interactions |
||||
if (parent.flags.has_focus) { |
||||
if (ctx.is_hovered(c_elem)) { |
||||
c_elem.flags.has_focus = true; |
||||
c_elem.events.mouse_hover = true; |
||||
bg_color = uint_to_rgba(0x00ff00ff); |
||||
c_elem.events.mouse_hold = ctx.input.mouse.down.btn_left; |
||||
} else { |
||||
c_elem.events.mouse_hover = false; |
||||
} |
||||
} |
||||
|
||||
// Draw the button |
||||
Cmd cmd = { |
||||
.type = CMD_RECT, |
||||
.rect = { |
||||
.rect = c_elem.rect, |
||||
.color = bg_color, |
||||
}, |
||||
}; |
||||
ctx.cmd_queue.enqueue(&cmd)!; |
||||
|
||||
return c_elem.events; |
||||
} |
@ -0,0 +1,78 @@ |
||||
module ugui; |
||||
|
||||
import std::io; |
||||
|
||||
// TODO: this could be a bitstruct |
||||
bitstruct InputEvents : uint { |
||||
bool resize : 0; // window size was changed |
||||
bool change_focus : 1; // window focus changed |
||||
bool mouse_move : 2; // mouse was moved |
||||
bool mouse_btn : 3; // mouse button pressed or released |
||||
} |
||||
|
||||
// Window size was changed |
||||
fn void! Ctx.input_window_size(&ctx, short width, short height) |
||||
{ |
||||
if (width <= 0 || height <= 0) { |
||||
return UgError.INVALID_SIZE?; |
||||
} |
||||
ctx.width = width; |
||||
ctx.height = height; |
||||
ctx.input.events.resize = true; |
||||
} |
||||
|
||||
// Window gained/lost focus |
||||
fn void Ctx.input_changefocus(&ctx, bool has_focus) |
||||
{ |
||||
// FIXME: raylib only has an API to query the focus status so we have to |
||||
// update the input flag only if the focus changed |
||||
if (ctx.has_focus != has_focus) { |
||||
ctx.input.events.change_focus = true; |
||||
} |
||||
ctx.has_focus = has_focus; |
||||
} |
||||
|
||||
bitstruct MouseButtons : uint { |
||||
bool btn_left : 0; |
||||
bool btn_middle : 1; |
||||
bool btn_right : 2; |
||||
bool btn_4 : 3; |
||||
bool btn_5 : 4; |
||||
} |
||||
|
||||
// Mouse Button moved |
||||
fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) |
||||
{ |
||||
ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons; |
||||
ctx.input.mouse.down = buttons; |
||||
ctx.input.events.mouse_btn = true; |
||||
|
||||
io::printfn( |
||||
"Mouse Down: %s%s%s%s%s", |
||||
buttons.btn_left ? "BTN_LEFT " : "", |
||||
buttons.btn_right ? "BTN_RIGHT " : "", |
||||
buttons.btn_middle ? "BTN_MIDDLE " : "", |
||||
buttons.btn_4 ? "BTN_4 " : "", |
||||
buttons.btn_5 ? "BTN_5 " : "" |
||||
); |
||||
} |
||||
|
||||
// Mouse was moved, report absolute position |
||||
// TODO: implement this |
||||
fn void Ctx.input_mouse_abs(&ctx, short x, short y) { return; } |
||||
|
||||
// Mouse was moved, report relative motion |
||||
fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) |
||||
{ |
||||
ctx.input.mouse.delta.x = dx; |
||||
ctx.input.mouse.delta.y = dy; |
||||
|
||||
short mx, my; |
||||
mx = ctx.input.mouse.pos.x + dx; |
||||
my = ctx.input.mouse.pos.y + dy; |
||||
|
||||
ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width); |
||||
ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height); |
||||
|
||||
ctx.input.events.mouse_move = true; |
||||
} |
@ -0,0 +1,153 @@ |
||||
module ugui; |
||||
|
||||
|
||||
fn void! Ctx.layout_set_row(&ctx) |
||||
{ |
||||
Id parent_id = ctx.tree.get(ctx.active_div)!; |
||||
Elem *parent = ctx.cache.search(parent_id)!; |
||||
|
||||
if (parent.type != ETYPE_DIV) { |
||||
// what? |
||||
return UgError.UNEXPECTED_ELEMENT?; |
||||
} |
||||
|
||||
parent.div.layout = LAYOUT_ROW; |
||||
} |
||||
|
||||
fn void! Ctx.layout_set_column(&ctx) |
||||
{ |
||||
Id parent_id = ctx.tree.get(ctx.active_div)!; |
||||
Elem *parent = ctx.cache.search(parent_id)!; |
||||
|
||||
if (parent.type != ETYPE_DIV) { |
||||
// what? |
||||
return UgError.UNEXPECTED_ELEMENT?; |
||||
} |
||||
|
||||
parent.div.layout = LAYOUT_COLUMN; |
||||
} |
||||
|
||||
fn void! Ctx.layout_set_floating(&ctx) |
||||
{ |
||||
Id parent_id = ctx.tree.get(ctx.active_div)!; |
||||
Elem *parent = ctx.cache.search(parent_id)!; |
||||
|
||||
if (parent.type != ETYPE_DIV) { |
||||
// what? |
||||
return UgError.UNEXPECTED_ELEMENT?; |
||||
} |
||||
|
||||
parent.div.layout = LAYOUT_FLOATING; |
||||
} |
||||
|
||||
fn void! Ctx.layout_next_row(&ctx) |
||||
{ |
||||
Id parent_id = ctx.tree.get(ctx.active_div)!; |
||||
Elem *parent = ctx.cache.search(parent_id)!; |
||||
|
||||
if (parent.type != ETYPE_DIV) { |
||||
// what? |
||||
return UgError.UNEXPECTED_ELEMENT?; |
||||
} |
||||
|
||||
parent.div.origin_r = Point{ |
||||
.x = parent.rect.x, |
||||
.y = parent.div.origin_c.y, |
||||
}; |
||||
parent.div.origin_c = parent.div.origin_r; |
||||
} |
||||
|
||||
fn void! Ctx.layout_next_column(&ctx) |
||||
{ |
||||
Id parent_id = ctx.tree.get(ctx.active_div)!; |
||||
Elem *parent = ctx.cache.search(parent_id)!; |
||||
|
||||
if (parent.type != ETYPE_DIV) { |
||||
// what? |
||||
return UgError.UNEXPECTED_ELEMENT?; |
||||
} |
||||
|
||||
parent.div.origin_c = Point{ |
||||
.x = parent.div.origin_r.x, |
||||
.y = parent.rect.y, |
||||
}; |
||||
parent.div.origin_r = parent.div.origin_c; |
||||
} |
||||
|
||||
// position the rectangle inside the parent according to the layout |
||||
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) |
||||
{ |
||||
Rect elem_rect; |
||||
Point origin; |
||||
|
||||
// 1. Select the right origin |
||||
switch (parent.div.layout) { |
||||
case LAYOUT_ROW: |
||||
origin = parent.div.origin_r; |
||||
case LAYOUT_COLUMN: |
||||
origin = parent.div.origin_c; |
||||
case LAYOUT_FLOATING: // none |
||||
default: |
||||
// Error |
||||
} |
||||
|
||||
// 2. Position the rect |
||||
elem_rect.x = origin.x + rect.x; |
||||
elem_rect.y = origin.y + rect.y; |
||||
|
||||
// 3. Calculate width & height |
||||
// TODO: what about negative values? |
||||
// FIXME: account for origin offset!! |
||||
elem_rect.w = rect.w > 0 ? rect.w : parent.rect.w; |
||||
elem_rect.h = rect.h > 0 ? rect.h : parent.rect.h; |
||||
|
||||
// 4. Update the origins of the parent |
||||
parent.div.origin_r = Point{ |
||||
.x = elem_rect.x + elem_rect.w, |
||||
.y = elem_rect.y, |
||||
}; |
||||
parent.div.origin_c = Point{ |
||||
.x = elem_rect.x, |
||||
.y = elem_rect.y + elem_rect.h, |
||||
}; |
||||
|
||||
// if using the style then apply margins |
||||
// FIXME: this does not work |
||||
if (style && parent.div.layout != LAYOUT_FLOATING) { |
||||
elem_rect.x += ctx.style.margin.x; |
||||
elem_rect.y += ctx.style.margin.y; |
||||
|
||||
// total keep-out borders |
||||
Rect margin_tot = { |
||||
.x = ctx.style.padding.x + ctx.style.border.x + |
||||
ctx.style.margin.x, |
||||
.y = ctx.style.padding.y + ctx.style.border.y + |
||||
ctx.style.margin.y, |
||||
.w = ctx.style.padding.w + ctx.style.border.x + |
||||
ctx.style.margin.w, |
||||
.h = ctx.style.padding.h + ctx.style.border.x + |
||||
ctx.style.margin.h, |
||||
}; |
||||
|
||||
parent.div.origin_r.x += margin_tot.x + margin_tot.w; |
||||
// parent.div.origin_r.y += margin_tot.h; |
||||
|
||||
// parent.div.origin_c.x += margin_tot.w; |
||||
parent.div.origin_c.y += margin_tot.y + margin_tot.h; |
||||
} |
||||
|
||||
/* |
||||
printf( |
||||
"positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d |
||||
%d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.rect.x, |
||||
parent.rect.y, |
||||
parent.rect.w, |
||||
parent.rect.h, |
||||
elem_rect.x, |
||||
elem_rect.y, |
||||
elem_rect.w, |
||||
elem_rect.h |
||||
); |
||||
*/ |
||||
return elem_rect; |
||||
} |
@ -0,0 +1,333 @@ |
||||
module vtree(<ElemType>); |
||||
|
||||
import std::core::mem; |
||||
import std::io; |
||||
|
||||
struct VTree { |
||||
usz elements; |
||||
ElemType[] vector; // vector of element ids |
||||
isz[] refs, ordered_refs; |
||||
} |
||||
|
||||
fault VTreeError { |
||||
CANNOT_SHRINK, |
||||
INVALID_REFERENCE, |
||||
TREE_FULL, |
||||
REFERENCE_NOT_PRESENT, |
||||
INVALID_ARGUMENT, |
||||
} |
||||
|
||||
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } |
||||
macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } |
||||
macro VTree.size(&tree) { return tree.refs.len; } |
||||
|
||||
// macro to zero an elemen |
||||
macro @zero() |
||||
{ |
||||
$if $assignable(0, ElemType): |
||||
return 0; |
||||
$else |
||||
return ElemType{0}; |
||||
$endif |
||||
} |
||||
|
||||
fn void! VTree.init(&tree, usz size) |
||||
{ |
||||
tree.vector = mem::new_array(ElemType, size); |
||||
defer catch { (void)mem::free(tree.vector); } |
||||
|
||||
tree.refs = mem::new_array(isz, size); |
||||
defer catch { (void)mem::free(tree.refs); } |
||||
|
||||
tree.ordered_refs = mem::new_array(isz, size); |
||||
defer catch { (void)mem::free(tree.ordered_refs); } |
||||
|
||||
// set all refs to -1, meaning invalid (free) element |
||||
tree.refs[..] = -1; |
||||
|
||||
tree.elements = 0; |
||||
} |
||||
|
||||
fn void VTree.free(&tree) |
||||
{ |
||||
(void)mem::free(tree.vector); |
||||
(void)mem::free(tree.refs); |
||||
(void)mem::free(tree.ordered_refs); |
||||
} |
||||
|
||||
fn void VTree.pack(&tree) |
||||
{ |
||||
// TODO: add a PACKED flag to skip this |
||||
|
||||
isz free_spot = -1; |
||||
for (usz i = 0; i < tree.size(); i++) { |
||||
if (tree.refs[i] == -1) { |
||||
free_spot = i; |
||||
continue; |
||||
} |
||||
|
||||
// find a item that can be packed |
||||
if (free_spot >= 0 && tree.refs[i] >= 0) { |
||||
isz old_ref = i; |
||||
|
||||
// move the item |
||||
tree.vector[free_spot] = tree.vector[i]; |
||||
tree.refs[free_spot] = tree.refs[i]; |
||||
|
||||
tree.vector[i] = @zero(); |
||||
tree.refs[i] = -1; |
||||
|
||||
// and move all references |
||||
for (usz j = 0; j < tree.size(); j++) { |
||||
if (tree.refs[j] == old_ref) { |
||||
tree.refs[j] = free_spot; |
||||
} |
||||
} |
||||
|
||||
// mark the free spot as used |
||||
free_spot = -1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn void! VTree.resize(&tree, usz newsize) |
||||
{ |
||||
// return error when shrinking with too many elements |
||||
if (newsize < tree.elements) { |
||||
return VTreeError.CANNOT_SHRINK?; |
||||
} |
||||
|
||||
// pack the vector when shrinking to avoid data loss |
||||
if ((int)newsize < tree.size()) { |
||||
// FIXME: packing destroys all references to elements of vec |
||||
// so shrinking may cause dangling pointers |
||||
return VTreeError.CANNOT_SHRINK?; |
||||
} |
||||
|
||||
usz old_size = tree.size(); |
||||
|
||||
tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize]; |
||||
defer catch { (void)mem::free(tree.vector); } |
||||
|
||||
tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize]; |
||||
defer catch { (void)mem::free(tree.refs); } |
||||
|
||||
tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize]; |
||||
defer catch { (void)mem::free(tree.ordered_refs); } |
||||
|
||||
if (newsize > tree.size()) { |
||||
tree.vector[old_size..newsize-1] = @zero(); |
||||
tree.refs[old_size..newsize-1] = -1; |
||||
} |
||||
} |
||||
|
||||
// add an element to the tree, return it's ref |
||||
fn isz! VTree.add(&tree, ElemType elem, isz parent) |
||||
{ |
||||
// invalid parent |
||||
if (!tree.ref_is_valid(parent)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
// no space left |
||||
if (tree.elements >= tree.size()) { |
||||
return VTreeError.TREE_FULL?; |
||||
} |
||||
|
||||
// check if the parent exists |
||||
// if there are no elements in the tree the first add will set the root |
||||
if (!tree.ref_is_present(parent) && tree.elements != 0) { |
||||
return VTreeError.REFERENCE_NOT_PRESENT?; |
||||
} |
||||
|
||||
// get the first free spot |
||||
isz free_spot = -1; |
||||
for (usz i = 0; i < tree.size(); i++) { |
||||
if (tree.refs[i] == -1) { |
||||
free_spot = i; |
||||
break; |
||||
} |
||||
} |
||||
if (free_spot < 0) { |
||||
return VTreeError.TREE_FULL?; |
||||
} |
||||
|
||||
// finally add the element |
||||
tree.vector[free_spot] = elem; |
||||
tree.refs[free_spot] = parent; |
||||
tree.elements++; |
||||
|
||||
return free_spot; |
||||
} |
||||
|
||||
// prune the tree starting from the ref |
||||
// returns the number of pruned elements |
||||
fn usz! VTree.prune(&tree, isz ref) |
||||
{ |
||||
if (!tree.ref_is_valid(ref)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
if (!tree.ref_is_present(ref)) { |
||||
return 0; |
||||
} |
||||
|
||||
tree.vector[ref] = @zero(); |
||||
tree.refs[ref] = -1; |
||||
tree.elements--; |
||||
|
||||
usz count = 1; |
||||
for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) { |
||||
if (tree.refs[i] == ref) { |
||||
count += tree.prune(i)!; |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// find the size of the subtree starting from ref |
||||
fn usz! VTree.subtree_size(&tree, isz ref) |
||||
{ |
||||
if (!tree.ref_is_valid(ref)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
if (!tree.ref_is_present(ref)) { |
||||
return 0; |
||||
} |
||||
|
||||
usz count = 1; |
||||
for (usz i = 0; i < tree.size(); i++) { |
||||
// only root has the reference to itself |
||||
if (tree.refs[i] == ref && ref != i) { |
||||
count += tree.subtree_size(i)!; |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// iterate through the first level children, use a cursor like strtok_r |
||||
fn isz! VTree.children_it(&tree, isz parent, isz *cursor) |
||||
{ |
||||
if (cursor == null) { |
||||
return VTreeError.INVALID_ARGUMENT?; |
||||
} |
||||
|
||||
// if the cursor is out of bounds then we are done for sure |
||||
if (!tree.ref_is_valid(*cursor)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
// same for the parent, if it's invalid it can't have children |
||||
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
// find the first child, update the cursor and return the ref |
||||
for (isz i = *cursor; i < tree.size(); i++) { |
||||
if (tree.refs[i] == parent) { |
||||
*cursor = i + 1; |
||||
return i; |
||||
} |
||||
} |
||||
|
||||
// if no children are found return -1 |
||||
*cursor = -1; |
||||
return -1; |
||||
} |
||||
|
||||
/* iterates trough every leaf of the subtree in the following manner |
||||
* node [x], x: visit order |
||||
* [0] |
||||
* / | \ |
||||
* / [2] [3] |
||||
* [1] | |
||||
* / \ [6] |
||||
* [4] [5] |
||||
*/ |
||||
fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) |
||||
{ |
||||
if (cursor == null) { |
||||
return VTreeError.INVALID_ARGUMENT?; |
||||
} |
||||
|
||||
isz[] queue = tree.ordered_refs; |
||||
|
||||
// TODO: this could also be done when adding or removing elements |
||||
// first call, create a ref array ordered like we desire |
||||
if (*cursor == -1) { |
||||
*cursor = 0; |
||||
queue[..] = -1; |
||||
|
||||
// iterate through the queue appending found children |
||||
isz pos, off; |
||||
do { |
||||
// printf ("ref=%d\n", ref); |
||||
for (isz i = 0; i < tree.size(); i++) { |
||||
if (tree.refs[i] == ref) { |
||||
queue[pos++] = i; |
||||
} |
||||
} |
||||
|
||||
for (; ref == queue[off] && off < tree.size(); off++); |
||||
ref = queue[off]; |
||||
|
||||
} while (tree.ref_is_valid(ref)); |
||||
// This line is why tree.ordered_refs has to be size+1 |
||||
queue[off + 1] = -1; |
||||
} |
||||
|
||||
// PRINT_ARR(queue, tree.size()); |
||||
// return -1; |
||||
|
||||
// on successive calls just iterate through the queue until we find an |
||||
// invalid ref, if the user set the cursor to -1 it means it has found what |
||||
// he needed, so free |
||||
if (*cursor < 0) { |
||||
return -1; |
||||
} else if (tree.ref_is_valid(*cursor)) { |
||||
return queue[(*cursor)++]; |
||||
} |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
fn isz! VTree.parentof(&tree, isz ref) |
||||
{ |
||||
if (!tree.ref_is_valid(ref)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
if (!tree.ref_is_present(ref)) { |
||||
return VTreeError.REFERENCE_NOT_PRESENT?; |
||||
} |
||||
|
||||
return tree.refs[ref]; |
||||
} |
||||
|
||||
fn ElemType! VTree.get(&tree, isz ref) |
||||
{ |
||||
if (!tree.ref_is_valid(ref)) { |
||||
return VTreeError.INVALID_REFERENCE?; |
||||
} |
||||
|
||||
if (!tree.ref_is_present(ref)) { |
||||
return VTreeError.REFERENCE_NOT_PRESENT?; |
||||
} |
||||
|
||||
return tree.vector[ref]; |
||||
} |
||||
|
||||
fn void VTree.print(&tree) |
||||
{ |
||||
for (isz i = 0; i < tree.size(); i++) { |
||||
if (tree.refs[i] == -1) { |
||||
continue; |
||||
} |
||||
io::printf("[%d] {parent=%d, data=", i, tree.refs[i]); |
||||
io::print(tree.vector[i]); |
||||
io::printn("}"); |
||||
} |
||||
} |
@ -1,137 +0,0 @@ |
||||
#ifndef _HASH_GENERIC |
||||
#define _HASH_GENERIC |
||||
|
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <stdint.h> |
||||
|
||||
|
||||
// FIXME: change the api to just one HASH_DECL to HASH_PROTO and HASH_DEFINE
|
||||
|
||||
|
||||
#define HASH_MAXSIZE 4096 |
||||
// for fibonacci hashing, 2^{32,64}/<golden ratio>
|
||||
#define HASH_RATIO32 ((uint64_t)2654435769u) |
||||
#define HASH_RATIO64 ((uint64_t)11400714819322457583u) |
||||
// salt for string hashing
|
||||
#define HASH_SALT ((uint64_t)0xbabb0cac) |
||||
|
||||
|
||||
/* Ready-made compares */ |
||||
static inline int hash_cmp_u32(uint32_t a, uint32_t b) { return a == b; } |
||||
static inline int hash_cmp_u64(uint64_t a, uint64_t b) { return a == b; } |
||||
static inline int hash_cmp_str(const char *a, const char *b) { return strcmp(a, b) == 0; } |
||||
|
||||
|
||||
/* Ready-made hashes */ |
||||
static inline uint32_t hash_u64(uint64_t c) |
||||
{ |
||||
return (uint64_t)((((uint64_t)c+HASH_SALT)*HASH_RATIO64)>>32); |
||||
} |
||||
static inline uint32_t hash_u32(uint32_t c) |
||||
{ |
||||
return (uint32_t)((((uint64_t)c<<31)*HASH_RATIO64)>>32); |
||||
} |
||||
static inline uint32_t hash_str(const char *s) |
||||
{ |
||||
uint32_t h = HASH_SALT; |
||||
const uint8_t *v = (const uint8_t *)(s); |
||||
for (int x = *s; x; x--) { |
||||
h += v[x-1]; |
||||
h += h << 10; |
||||
h ^= h >> 6; |
||||
} |
||||
h += h << 3; |
||||
h ^= h >> 11; |
||||
h += h << 15; |
||||
return h; |
||||
} |
||||
|
||||
|
||||
#define HASH_DECL(htname, codetype, datatype, hashfn, cmpfn) \ |
||||
struct htname##_entry { \
|
||||
codetype code; \
|
||||
datatype data; \
|
||||
}; \
|
||||
\
|
||||
struct htname##_ref { \
|
||||
uint32_t items, size, exp; \
|
||||
struct htname##_entry bucket[]; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
struct htname##_ref * htname##_create(uint32_t size) \
|
||||
{ \
|
||||
if (!size || size > HASH_MAXSIZE) \
|
||||
return NULL; \
|
||||
/* round to the greater power of two */ \
|
||||
/* FIXME: check for intger overflow here */ \
|
||||
uint32_t exp = 32-__builtin_clz(size-1); \
|
||||
size = 1<<(exp); \
|
||||
/* FIXME: check for intger overflow here */ \
|
||||
struct htname##_ref *ht = malloc(sizeof(struct htname##_ref)+sizeof(struct htname##_entry)*size); \
|
||||
if (ht) { \
|
||||
ht->items = 0; \
|
||||
ht->size = size; \
|
||||
ht->exp = exp; \
|
||||
memset(ht->bucket, 0, sizeof(struct htname##_entry)*size); \
|
||||
} \
|
||||
return ht; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
void htname##_destroy(struct htname##_ref *ht) \
|
||||
{ \
|
||||
if (ht) free(ht); \
|
||||
} \
|
||||
\
|
||||
\
|
||||
static uint32_t htname##_lookup(struct htname##_ref *ht, uint32_t hash, uint32_t idx) \
|
||||
{ \
|
||||
if (!ht) return 0; \
|
||||
uint32_t mask = ht->size-1; \
|
||||
uint32_t step = (hash >> (32 - ht->exp)) | 1; \
|
||||
return (idx + step) & mask; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/* Find and return the element by code */ \
|
||||
struct htname##_entry * htname##_search(struct htname##_ref *ht, codetype code)\
|
||||
{ \
|
||||
if (!ht) return NULL; \
|
||||
uint32_t h = hashfn(code); \
|
||||
for (uint32_t i=h, x=0; ; x++) { \
|
||||
i = htname##_lookup(ht, h, i); \
|
||||
if (x > (ht->size<<1) || \
|
||||
!ht->bucket[i].code || \
|
||||
cmpfn(ht->bucket[i].code, code) \
|
||||
) { \
|
||||
return &(ht->bucket[i]); \
|
||||
} \
|
||||
} \
|
||||
return NULL; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/* FIXME: this simply overrides the found item */ \
|
||||
struct htname##_entry * htname##_insert(struct htname##_ref *ht, struct htname##_entry *entry) \
|
||||
{ \
|
||||
struct htname##_entry *r = htname##_search(ht, entry->code); \
|
||||
if (r) { \
|
||||
if (!r->code) \
|
||||
ht->items++; \
|
||||
*r = *entry; \
|
||||
} \
|
||||
return r; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
struct htname##_entry * htname##_remove(struct htname##_ref *ht, codetype code)\
|
||||
{ \
|
||||
if (!ht) return NULL; \
|
||||
struct htname##_entry *r = htname##_search(ht, code); \
|
||||
if (r) r->code = 0; \
|
||||
return r; \
|
||||
} \
|
||||
|
||||
#endif |
@ -1,118 +0,0 @@ |
||||
#ifndef _STACK_GENERIC_H |
||||
#define _STACK_GENERIC_H |
||||
|
||||
|
||||
#define STACK_STEP 8 |
||||
#define STACK_SALT 0xbabb0cac |
||||
|
||||
// FIXME: find a way to not re-hash the whole stack when removing one item
|
||||
|
||||
// incremental hash for every grow
|
||||
#if STACK_DISABLE_HASH |
||||
#define STACK_HASH(p, s, h) {} |
||||
#else |
||||
#define STACK_HASH(p, s, h) \ |
||||
{ \
|
||||
unsigned char *v = (unsigned char *)(p); \
|
||||
for (int x = (s); x; x--) { \
|
||||
(h) += v[x-1]; \
|
||||
(h) += (h) << 10; \
|
||||
(h) ^= (h) >> 6; \
|
||||
} \
|
||||
(h) += (h) << 3; \
|
||||
(h) ^= (h) >> 11; \
|
||||
(h) += (h) << 15; \
|
||||
} |
||||
#endif |
||||
|
||||
|
||||
// TODO: add a rolling hash
|
||||
#define STACK_DECL(stackname, type) \ |
||||
struct stackname { \
|
||||
type *items; \
|
||||
int size, idx, old_idx; \
|
||||
unsigned int hash, old_hash; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
struct stackname stackname##_init(void) \
|
||||
{ \
|
||||
return (struct stackname){0, .hash = STACK_SALT}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_grow(struct stackname *stack, int step) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \
|
||||
if(!stack->items) \
|
||||
return -1; \
|
||||
memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \
|
||||
stack->size += step; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_push(struct stackname *stack, type *e) \
|
||||
{ \
|
||||
if (!stack || !e) \
|
||||
return -1; \
|
||||
if (stack->idx >= stack->size) \
|
||||
if (stackname##_grow(stack, STACK_STEP)) \
|
||||
return -1; \
|
||||
stack->items[stack->idx++] = *e; \
|
||||
STACK_HASH(e, sizeof(type), stack->hash); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
type stackname##_pop(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack || stack->idx == 0 || stack->size == 0) \
|
||||
return (type){0}; \
|
||||
stack->hash = STACK_SALT; \
|
||||
STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \
|
||||
return stack->items[stack->idx--]; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_clear(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->old_idx = stack->idx; \
|
||||
stack->old_hash = stack->hash; \
|
||||
stack->hash = STACK_SALT; \
|
||||
stack->idx = 0; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->hash != stack->old_hash; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_size_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->size != stack->old_idx; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_free(struct stackname *stack) \
|
||||
{ \
|
||||
if (stack) { \
|
||||
stackname##_clear(stack); \
|
||||
if (stack->items) \
|
||||
free(stack->items); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
|
||||
#endif |
@ -1,214 +0,0 @@ |
||||
//#!/usr/bin/tcc -run
|
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
|
||||
#define HASH_SALT (0xbabb0cac) |
||||
#define HASH_RATIO64 ((unsigned long int)11400714819322457583u) |
||||
|
||||
|
||||
struct ntree_node |
||||
{ |
||||
unsigned int id; |
||||
const char *name; |
||||
int children_no, children_size; |
||||
struct ntree_node *children, *parent; |
||||
}; |
||||
|
||||
|
||||
static char tmp_name_buffer[16] = {"node:"}; |
||||
|
||||
|
||||
unsigned int hash_str_to_uint(const char *s) |
||||
{ |
||||
unsigned int h = HASH_SALT; |
||||
const unsigned char *v = (const unsigned char *)(s); |
||||
for (int x = *s; x; x--) { |
||||
h += v[x-1]; |
||||
h += h << 10; |
||||
h ^= h >> 6; |
||||
} |
||||
h += h << 3; |
||||
h ^= h >> 11; |
||||
h += h << 15; |
||||
return h; |
||||
} |
||||
|
||||
|
||||
unsigned int hash_u32(unsigned int c) |
||||
{ |
||||
return (unsigned int)((((unsigned long int)c<<31)*HASH_RATIO64)>>32); |
||||
} |
||||
|
||||
|
||||
const char * generate_new_name(void) |
||||
{ |
||||
static int count = 0; |
||||
unsigned int h = hash_u32(count++); |
||||
snprintf(tmp_name_buffer+sizeof("node:"), 16-sizeof("node:"), "%x", h); |
||||
return tmp_name_buffer; |
||||
} |
||||
|
||||
|
||||
int tree_prune(struct ntree_node *node) |
||||
{ |
||||
if (node == NULL) |
||||
return 0; |
||||
|
||||
if ((node->children_no == 0 && node->children != NULL) || |
||||
(node->children_no != 0 && node->children == NULL)) { |
||||
printf("ERR: (%x %s) Inconsistent node children", node->id, node->name); |
||||
} else for (int i = 0; i < node->children_no; i++) { |
||||
tree_prune(&(node->children[i])); |
||||
} |
||||
|
||||
if (node->children != NULL) { |
||||
free(node->children); |
||||
} |
||||
free((void *)node->name); |
||||
if (node->parent != NULL) |
||||
node->parent->children_no--; |
||||
*node = (struct ntree_node){0}; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
struct ntree_node * tree_append(struct ntree_node *parent, const char *name) |
||||
{ |
||||
if (name == NULL) |
||||
return NULL; |
||||
if (strlen(name) == 0) |
||||
name = generate_new_name(); |
||||
|
||||
// generate the children information
|
||||
unsigned int id = hash_str_to_uint(name); |
||||
char *str = malloc(strlen(name)+1); |
||||
if (str == NULL) |
||||
return NULL; |
||||
strcpy(str, name); |
||||
|
||||
// grow the parent buffer if necessary
|
||||
if (parent->children_no >= parent->children_size) { |
||||
struct ntree_node *temp = NULL; |
||||
temp = realloc(parent->children, (parent->children_size+1)*sizeof(struct ntree_node)); |
||||
if (temp == NULL) { |
||||
free(str); |
||||
return NULL; |
||||
} |
||||
|
||||
parent->children = temp; |
||||
parent->children[parent->children_size] = (struct ntree_node){0}; |
||||
parent->children_size++; |
||||
} |
||||
|
||||
// find an open spot for the child
|
||||
struct ntree_node *child = NULL; |
||||
for (int i = 0; i < parent->children_size; i++) { |
||||
if (parent->children[i].id == 0) |
||||
child = &(parent->children[i]); |
||||
} |
||||
if (child == NULL) |
||||
return NULL; |
||||
|
||||
child->name = str; |
||||
child->id = id; |
||||
child->children = NULL; |
||||
child->children_no = 0; |
||||
child->parent = parent; |
||||
parent->children_no++; |
||||
//printf("append to %s, children: %d\n", parent->name, parent->children_no);
|
||||
return child; |
||||
} |
||||
|
||||
|
||||
struct ntree_node * tree_find_id(struct ntree_node *root, unsigned int id) |
||||
{ |
||||
if (id == 0 || root == NULL) |
||||
return NULL; |
||||
|
||||
if (root->id == id) |
||||
return root; |
||||
|
||||
else for (int i = 0; i < root->children_size; i++) { |
||||
if (root->children[i].id != 0 && tree_find_id(&(root->children[i]), id) != NULL) |
||||
return &(root->children[i]); |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
|
||||
// TODO: add a foreach_child function
|
||||
struct ntree_node * tree_find(struct ntree_node *root, const char *name) |
||||
{ |
||||
if (name == NULL || strlen(name) == 0 || root == NULL) |
||||
return NULL; |
||||
unsigned int id = hash_str_to_uint(name); |
||||
return tree_find_id(root, id); |
||||
} |
||||
|
||||
|
||||
int tree_size(struct ntree_node *root) |
||||
{ |
||||
if (root == NULL) |
||||
return 0; |
||||
|
||||
int count = root->children_no; |
||||
if (count > 0) { |
||||
for (int i = 0; i < root->children_size; i++) { |
||||
if (root->children[i].id != 0) |
||||
count += tree_size(&(root->children[i])); |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
|
||||
static int tree_print_ind(struct ntree_node *node, int dd) |
||||
{ |
||||
for (int i = 0; i < dd; i++) printf(" "); |
||||
printf("[%s]\n", node->name); |
||||
for (int i = 0; i < node->children_size; i++) { |
||||
if (node->children[i].id == 0) continue; |
||||
tree_print_ind(&(node->children[i]), dd+1); |
||||
} |
||||
return node->children_no; |
||||
} |
||||
|
||||
|
||||
int tree_print(struct ntree_node *root) |
||||
{ |
||||
if (root == NULL) |
||||
return 1; |
||||
|
||||
tree_print_ind(root, 0); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
int main(void) |
||||
{ |
||||
char *root_name = malloc(sizeof("root")); |
||||
strcpy(root_name, "root"); |
||||
struct ntree_node root = {.name = root_name}; |
||||
struct ntree_node *n; |
||||
|
||||
n = tree_append(&root, "node 0:0"); |
||||
tree_append(n, "node 0:0:0"); |
||||
tree_append(&root, "node 0:1"); |
||||
tree_append(&root, "node 0:2"); |
||||
printf("Number of nodes %d\n", tree_size(&root)); |
||||
tree_print(&root); |
||||
|
||||
tree_prune(tree_find(&root, "node 0:0")); |
||||
printf("Number of nodes %d\n", tree_size(&root)); |
||||
|
||||
tree_prune(&root); |
||||
printf("Number of nodes %d\n", tree_size(&root)); |
||||
|
||||
return 0; |
||||
} |
@ -1,348 +0,0 @@ |
||||
#ifndef _VECTREE_H |
||||
#define _VECTREE_H |
||||
|
||||
#ifdef VTREE_DTYPE |
||||
|
||||
typedef struct { |
||||
int size, elements; |
||||
VTREE_DTYPE *vector; |
||||
int *refs; |
||||
} Vtree; |
||||
|
||||
int vtree_init(Vtree *tree, unsigned int size);
|
||||
int vtree_pack(Vtree *tree); |
||||
int vtree_resize(Vtree *tree, unsigned int newsize); |
||||
int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent); |
||||
int vtree_prune(Vtree *tree, int ref); |
||||
int vtree_subtree_size(Vtree *tree, int ref); |
||||
int vtree_children_it(Vtree *tree, int parent, int *cursor); |
||||
int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor); |
||||
int vtree_destroy(Vtree *tree); |
||||
|
||||
#ifdef VTREE_IMPL |
||||
|
||||
#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) |
||||
#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) |
||||
|
||||
int vtree_init(Vtree *tree, unsigned int size)
|
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
tree->vector = malloc(sizeof(VTREE_DTYPE) * size); |
||||
if (tree->vector == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
tree->refs = malloc(sizeof(int) * size); |
||||
if (tree->refs == NULL) { |
||||
free(tree->vector); |
||||
return -1; |
||||
} |
||||
|
||||
// set all refs to -1, meaning invalid (free) element
|
||||
for (unsigned int i = 0; i < size; i++) { |
||||
tree->refs[i] = -1; |
||||
} |
||||
|
||||
// fill vector with zeroes
|
||||
memset(tree->vector, 0, size * sizeof(VTREE_DTYPE)); |
||||
|
||||
tree->size = size; |
||||
tree->elements = 0; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int vtree_destroy(Vtree *tree) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
free(tree->vector); |
||||
free(tree->refs); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int vtree_pack(Vtree *tree) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// TODO: add a PACKED flag to skip this
|
||||
|
||||
int free_spot = -1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == -1) { |
||||
free_spot = i; |
||||
continue; |
||||
} |
||||
|
||||
// find a item that can be packed
|
||||
if (free_spot >= 0 && tree->refs[i] >= 0) { |
||||
int old_ref = i; |
||||
|
||||
// move the item
|
||||
tree->vector[free_spot] = tree->vector[i]; |
||||
tree->refs[free_spot] = tree->refs[i]; |
||||
|
||||
tree->vector[i] = (VTREE_DTYPE){0}; |
||||
tree->refs[i] = -1; |
||||
|
||||
// and move all references
|
||||
for (int x = 0; x < tree->size; x++) { |
||||
if (tree->refs[x] == old_ref) { |
||||
tree->refs[x] = free_spot; |
||||
} |
||||
} |
||||
|
||||
// mark the free spot as used
|
||||
free_spot = -1; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int vtree_resize(Vtree *tree, unsigned int newsize)
|
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// return error when shrinking with too many elements
|
||||
if ((int)newsize < tree->elements) { |
||||
return -1; |
||||
} |
||||
|
||||
// pack the vector when shrinking to avoid data loss
|
||||
if ((int)newsize < tree->size) { |
||||
//if (vtree_pack(tree) < 0) {
|
||||
// return -1;
|
||||
//}
|
||||
// TODO: allow shrinking, since packing destroys all references
|
||||
return -1; |
||||
} |
||||
|
||||
VTREE_DTYPE *newvec = realloc(tree->vector, newsize * sizeof(VTREE_DTYPE)); |
||||
if (newvec == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
int *newrefs = realloc(tree->refs, newsize * sizeof(int)); |
||||
if (newrefs == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
tree->vector = newvec; |
||||
tree->refs = newrefs; |
||||
if ((int)newsize > tree->size) { |
||||
for (int i = tree->size; i < (int)newsize; i++) { |
||||
tree->vector[i] = (VTREE_DTYPE){0}; |
||||
tree->refs[i] = -1; |
||||
} |
||||
} |
||||
|
||||
tree->size = newsize; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
// add an element to the tree, return it's ref
|
||||
int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// invalid parent
|
||||
if (!IS_VALID_REF(tree, parent)) { |
||||
return -1; |
||||
} |
||||
|
||||
// no space left
|
||||
if (tree->elements >= tree->size) { |
||||
return -1; |
||||
} |
||||
|
||||
// check if the parent exists
|
||||
// if there are no elements in the tree the first add will set the root
|
||||
if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { |
||||
return -1; |
||||
} |
||||
|
||||
// get the first free spot
|
||||
int free_spot = -1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == -1) { |
||||
free_spot = i; |
||||
break; |
||||
} |
||||
} |
||||
if (free_spot < 0) { |
||||
return -1; |
||||
} |
||||
|
||||
// finally add the element
|
||||
tree->vector[free_spot] = elem; |
||||
tree->refs[free_spot] = parent; |
||||
tree->elements++; |
||||
|
||||
return free_spot; |
||||
} |
||||
|
||||
// prune the tree starting from the ref
|
||||
// returns the number of pruned elements
|
||||
int vtree_prune(Vtree *tree, int ref) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!IS_VALID_REF(tree, ref)) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!REF_IS_PRESENT(tree, ref)) { |
||||
return 0; |
||||
} |
||||
|
||||
tree->vector[ref] = (VTREE_DTYPE){0}; |
||||
tree->refs[ref] = -1; |
||||
|
||||
int count = 1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == ref) { |
||||
count += vtree_prune(tree, i); |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// find the size of the subtree starting from ref
|
||||
int vtree_subtree_size(Vtree *tree, int ref) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!IS_VALID_REF(tree, ref)) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!REF_IS_PRESENT(tree, ref)) { |
||||
return 0; |
||||
} |
||||
|
||||
int count = 1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
// only root has the reference to itself
|
||||
if (tree->refs[i] == ref && ref != i) { |
||||
count += vtree_subtree_size(tree, i); |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// iterate through the first level children, use a cursor like strtok_r
|
||||
int vtree_children_it(Vtree *tree, int parent, int *cursor) |
||||
{ |
||||
if (tree == NULL || cursor == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// if the cursor is out of bounds then we are done for sure
|
||||
if (!IS_VALID_REF(tree, *cursor)) { |
||||
return -1; |
||||
} |
||||
|
||||
// same for the parent, if it's invalid it can't have children
|
||||
if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { |
||||
return -1; |
||||
} |
||||
|
||||
// find the first child, update the cursor and return the ref
|
||||
for (int i = *cursor; i < tree->size; i++) { |
||||
if (tree->refs[i] == parent) { |
||||
*cursor = i + 1; |
||||
return i; |
||||
} |
||||
} |
||||
|
||||
// if no children are found return -1
|
||||
*cursor = -1; |
||||
return -1; |
||||
} |
||||
|
||||
/* iterates trough every leaf of the subtree in the following manner
|
||||
* node [x], x: visit order |
||||
* [0] |
||||
* / | \
|
||||
* / [2] [3] |
||||
* [1] | |
||||
* / \ [6] |
||||
* [4] [5] |
||||
*/ |
||||
int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor) |
||||
{ |
||||
if (tree == NULL || queue_p == NULL || cursor == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
int *queue = *queue_p; |
||||
|
||||
// TODO: this could also be done when adding or removing elements
|
||||
// first call, create a ref array ordered like we desire
|
||||
if (queue == NULL) { |
||||
*cursor = 0; |
||||
// create a queue of invalid refs, size is the worst case
|
||||
queue = malloc(sizeof(int) * tree->size); |
||||
if (queue == NULL) { |
||||
return -1; |
||||
} |
||||
for (int i = 0; i < tree->size; i++) { |
||||
queue[i] = -1; |
||||
} |
||||
*queue_p = queue; |
||||
|
||||
// iterate through the queue appending found children
|
||||
int pos = 0, off = 0; |
||||
do { |
||||
//printf ("ref=%d\n", ref);
|
||||
|
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == ref) { |
||||
queue[pos++] = i; |
||||
} |
||||
} |
||||
|
||||
for (;ref == queue[off] && off < tree->size; off++); |
||||
ref = queue[off]; |
||||
|
||||
} while (IS_VALID_REF(tree, ref)); |
||||
} |
||||
|
||||
//PRINT_ARR(queue, tree->size);
|
||||
//return -1;
|
||||
|
||||
// on successive calls just iterate through the queue until we find an
|
||||
// invalid ref
|
||||
int ret = queue[(*cursor)++]; |
||||
if (!IS_VALID_REF(tree, ret)) { |
||||
free(queue); |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
#endif // VTREE_IMPL
|
||||
|
||||
#endif // VTREE_DTYPE
|
||||
|
||||
#endif |
||||
|
@ -0,0 +1,14 @@ |
||||
bitstruct Bits : uint { |
||||
bool a : 0; |
||||
bool b : 1; |
||||
bool c : 2; |
||||
} |
||||
|
||||
fn int main() |
||||
{ |
||||
Bits a = {false, true, false}; |
||||
Bits b = {true, true, false}; |
||||
Bits c = a | b; |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,26 @@ |
||||
|
||||
struct CmdA { |
||||
int a, b; |
||||
} |
||||
|
||||
struct CmdB { |
||||
float a, b; |
||||
} |
||||
|
||||
union AnyCmd { |
||||
CmdA a; |
||||
CmdB b; |
||||
} |
||||
|
||||
struct Cmd { |
||||
int type; |
||||
AnyCmd cmd; |
||||
} |
||||
|
||||
fn int main() |
||||
{ |
||||
Cmd c; |
||||
c.type = 1; |
||||
c.cmd.a = {.a = 1, .b = 2}; |
||||
return 0; |
||||
} |
@ -0,0 +1,7 @@ |
||||
import std::io; |
||||
import vtree; |
||||
|
||||
fn int main() |
||||
{ |
||||
return 0; |
||||
} |
@ -1,69 +0,0 @@ |
||||
#define _POSIX_C_SOURCE 200809l |
||||
|
||||
#include <time.h> |
||||
|
||||
#include "timer.h" |
||||
|
||||
const clockid_t CLOCK_ID = CLOCK_PROCESS_CPUTIME_ID; |
||||
|
||||
struct _timer { |
||||
struct timespec start, stop; |
||||
struct timespec times[TIMER_MAX_PARTIAL]; |
||||
} timer = {0}; |
||||
|
||||
int timer_start(void) { return clock_gettime(CLOCK_ID, &timer.start); } |
||||
|
||||
int timer_reset(void) |
||||
{ |
||||
timer = (struct _timer) {0}; |
||||
return 0; |
||||
} |
||||
|
||||
int timer_stop(void) { return clock_gettime(CLOCK_ID, &timer.stop); } |
||||
|
||||
// partial clocks also update the stop time
|
||||
int timer_partial(int idx) |
||||
{ |
||||
if (idx > TIMER_MAX_PARTIAL || idx < 0) { |
||||
return -1; |
||||
} |
||||
clock_gettime(CLOCK_ID, &timer.stop); |
||||
return clock_gettime(CLOCK_ID, &(timer.times[idx])); |
||||
} |
||||
|
||||
size_t timer_get_us(int idx) |
||||
{ |
||||
if (idx > TIMER_MAX_PARTIAL) { |
||||
return -1; |
||||
} |
||||
|
||||
struct timespec ts = {0}; |
||||
if (idx < 0) { |
||||
ts = timer.stop; |
||||
} else { |
||||
ts = timer.times[idx]; |
||||
} |
||||
ts.tv_sec -= timer.start.tv_sec; |
||||
ts.tv_nsec -= timer.start.tv_nsec; |
||||
|
||||
// FIXME: check overflow
|
||||
return (ts.tv_nsec / 1000) + (ts.tv_sec * 1000000); |
||||
} |
||||
|
||||
double timer_get_sec(int idx) |
||||
{ |
||||
if (idx > TIMER_MAX_PARTIAL) { |
||||
return -1; |
||||
} |
||||
|
||||
struct timespec ts = {0}; |
||||
if (idx < 0) { |
||||
ts = timer.stop; |
||||
} else { |
||||
ts = timer.times[idx]; |
||||
} |
||||
ts.tv_sec -= timer.start.tv_sec; |
||||
ts.tv_nsec -= timer.start.tv_nsec; |
||||
|
||||
return (double)ts.tv_sec + ((double)ts.tv_nsec / 1e9); |
||||
} |
@ -1,16 +0,0 @@ |
||||
#ifndef UG_TIMER_H_ |
||||
#define UG_TIMER_H_ |
||||
|
||||
#include <stdlib.h> |
||||
|
||||
#define TIMER_MAX_PARTIAL 10 |
||||
|
||||
int timer_start(void); |
||||
int timer_stop(void); |
||||
int timer_reset(void); |
||||
int timer_partial(int idx); |
||||
|
||||
size_t timer_get_us(int idx); |
||||
double timer_get_sec(int idx); |
||||
|
||||
#endif |
@ -1,105 +0,0 @@ |
||||
#ifndef _UGUI_H |
||||
#define _UGUI_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
typedef struct { |
||||
int32_t x, y, w, h; |
||||
} UgRect; |
||||
|
||||
typedef struct { |
||||
int32_t x, y; |
||||
} UgPoint; |
||||
|
||||
typedef struct { |
||||
uint8_t r, g, b, a; |
||||
} UgColor; |
||||
|
||||
typedef uint64_t UgId; |
||||
|
||||
typedef enum { |
||||
ETYPE_NONE = 0, |
||||
ETYPE_DIV, |
||||
ETYPE_BUTTON, |
||||
} UgElemType; |
||||
|
||||
enum UgElemFlags { |
||||
ELEM_UPDATED = 1 << 0, |
||||
ELEM_HASFOCUS = 1 << 1, |
||||
}; |
||||
|
||||
enum UgElemEvent { |
||||
EVENT_KEY_PRESS = 1 << 0, |
||||
EVENT_KEY_RELEASE = 1 << 1, |
||||
EVENT_KEY_HOLD = 1 << 2, |
||||
EVENT_MOUSE_HOVER = 1 << 3, |
||||
EVENT_MOUSE_PRESS = 1 << 4, |
||||
EVENT_MOUSE_RELEASE = 1 << 5, |
||||
EVENT_MOUSE_HOLD = 1 << 6, |
||||
}; |
||||
|
||||
typedef struct { |
||||
UgId id; |
||||
uint32_t flags; |
||||
uint32_t event; |
||||
UgRect rect; |
||||
UgElemType type; |
||||
|
||||
// type-specific fields
|
||||
union { |
||||
struct UgDiv { |
||||
enum { |
||||
DIV_LAYOUT_ROW = 0, |
||||
DIV_LAYOUT_COLUMN, |
||||
DIV_LAYOUT_FLOATING, |
||||
} layout; |
||||
|
||||
UgPoint origin_r, origin_c; |
||||
UgColor color_bg; |
||||
} div; // Div
|
||||
}; |
||||
} UgElem; |
||||
|
||||
// TODO: add a packed flag
|
||||
// TODO: add a fill index to skip some searching for free spots
|
||||
|
||||
typedef struct { |
||||
int size, elements; |
||||
UgId *vector; // vector of element ids
|
||||
int *refs, *ordered_refs; |
||||
} UgTree; |
||||
|
||||
typedef struct { |
||||
struct _IdTable *table; |
||||
UgElem *array; |
||||
uint64_t *present, *used; |
||||
int cycles; |
||||
} UgElemCache; |
||||
|
||||
typedef struct _UgCtx UgCtx; |
||||
|
||||
// tree implementation
|
||||
int ug_tree_init(UgTree *tree, unsigned int size); |
||||
int ug_tree_pack(UgTree *tree); |
||||
int ug_tree_resize(UgTree *tree, unsigned int newsize); |
||||
int ug_tree_add(UgTree *tree, UgId elem, int parent); |
||||
int ug_tree_prune(UgTree *tree, int ref); |
||||
int ug_tree_subtree_size(UgTree *tree, int ref); |
||||
int ug_tree_children_it(UgTree *tree, int parent, int *cursor); |
||||
int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); |
||||
int ug_tree_parentof(UgTree *tree, int node); |
||||
int ug_tree_destroy(UgTree *tree); |
||||
UgId ug_tree_get(UgTree *tree, int node); |
||||
|
||||
// cache implementation
|
||||
UgElemCache ug_cache_init(void); |
||||
void ug_cache_free(UgElemCache *cache); |
||||
UgElem *ug_cache_search(UgElemCache *cache, UgId id); |
||||
UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index); |
||||
|
||||
int ug_init(UgCtx *ctx); |
||||
int ug_destroy(UgCtx *ctx); |
||||
int ug_frame_begin(UgCtx *ctx); |
||||
int ug_frame_end(UgCtx *ctx); |
||||
|
||||
#endif // _UGUI_H
|
@ -1,355 +0,0 @@ |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include "ugui.h" |
||||
|
||||
#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) |
||||
#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) |
||||
|
||||
int ug_tree_init(UgTree *tree, unsigned int size) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
tree->vector = malloc(sizeof(UgId) * size); |
||||
if (tree->vector == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
tree->refs = malloc(sizeof(int) * size); |
||||
if (tree->refs == NULL) { |
||||
free(tree->vector); |
||||
return -1; |
||||
} |
||||
|
||||
// ordered refs are used in the iterators
|
||||
tree->ordered_refs = malloc(sizeof(int) * (size + 1)); |
||||
if (tree->ordered_refs == NULL) { |
||||
free(tree->vector); |
||||
free(tree->refs); |
||||
return -1; |
||||
} |
||||
|
||||
// set all refs to -1, meaning invalid (free) element
|
||||
for (unsigned int i = 0; i < size; i++) { |
||||
tree->refs[i] = -1; |
||||
} |
||||
|
||||
// fill vector with zeroes
|
||||
memset(tree->vector, 0, size * sizeof(UgId)); |
||||
|
||||
tree->size = size; |
||||
tree->elements = 0; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int ug_tree_destroy(UgTree *tree) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
free(tree->vector); |
||||
free(tree->refs); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int ug_tree_pack(UgTree *tree) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// TODO: add a PACKED flag to skip this
|
||||
|
||||
int free_spot = -1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == -1) { |
||||
free_spot = i; |
||||
continue; |
||||
} |
||||
|
||||
// find a item that can be packed
|
||||
if (free_spot >= 0 && tree->refs[i] >= 0) { |
||||
int old_ref = i; |
||||
|
||||
// move the item
|
||||
tree->vector[free_spot] = tree->vector[i]; |
||||
tree->refs[free_spot] = tree->refs[i]; |
||||
|
||||
tree->vector[i] = 0; |
||||
tree->refs[i] = -1; |
||||
|
||||
// and move all references
|
||||
for (int x = 0; x < tree->size; x++) { |
||||
if (tree->refs[x] == old_ref) { |
||||
tree->refs[x] = free_spot; |
||||
} |
||||
} |
||||
|
||||
// mark the free spot as used
|
||||
free_spot = -1; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int ug_tree_resize(UgTree *tree, unsigned int newsize) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// return error when shrinking with too many elements
|
||||
if ((int)newsize < tree->elements) { |
||||
return -1; |
||||
} |
||||
|
||||
// pack the vector when shrinking to avoid data loss
|
||||
if ((int)newsize < tree->size) { |
||||
// if (ug_tree_pack(tree) < 0) {
|
||||
// return -1;
|
||||
// }
|
||||
// TODO: allow shrinking, since packing destroys all references
|
||||
return -1; |
||||
} |
||||
|
||||
UgId *newvec = realloc(tree->vector, newsize * sizeof(UgId)); |
||||
if (newvec == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
int *newrefs = realloc(tree->refs, newsize * sizeof(int)); |
||||
if (newrefs == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
int *neworrefs = realloc(tree->ordered_refs, (newsize + 1) * sizeof(int)); |
||||
if (neworrefs == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
tree->vector = newvec; |
||||
tree->refs = newrefs; |
||||
tree->ordered_refs = neworrefs; |
||||
if ((int)newsize > tree->size) { |
||||
for (int i = tree->size; i < (int)newsize; i++) { |
||||
tree->vector[i] = 0; |
||||
tree->refs[i] = -1; |
||||
} |
||||
} |
||||
|
||||
tree->size = newsize; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
// add an element to the tree, return it's ref
|
||||
int ug_tree_add(UgTree *tree, UgId elem, int parent) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// invalid parent
|
||||
if (!IS_VALID_REF(tree, parent)) { |
||||
return -1; |
||||
} |
||||
|
||||
// no space left
|
||||
if (tree->elements >= tree->size) { |
||||
return -1; |
||||
} |
||||
|
||||
// check if the parent exists
|
||||
// if there are no elements in the tree the first add will set the root
|
||||
if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { |
||||
return -1; |
||||
} |
||||
|
||||
// get the first free spot
|
||||
int free_spot = -1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == -1) { |
||||
free_spot = i; |
||||
break; |
||||
} |
||||
} |
||||
if (free_spot < 0) { |
||||
return -1; |
||||
} |
||||
|
||||
// finally add the element
|
||||
tree->vector[free_spot] = elem; |
||||
tree->refs[free_spot] = parent; |
||||
tree->elements++; |
||||
|
||||
return free_spot; |
||||
} |
||||
|
||||
// prune the tree starting from the ref
|
||||
// returns the number of pruned elements
|
||||
int ug_tree_prune(UgTree *tree, int ref) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!IS_VALID_REF(tree, ref)) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!REF_IS_PRESENT(tree, ref)) { |
||||
return 0; |
||||
} |
||||
|
||||
tree->vector[ref] = 0; |
||||
tree->refs[ref] = -1; |
||||
tree->elements--; |
||||
|
||||
int count = 1; |
||||
for (int i = 0; tree->elements > 0 && i < tree->size; i++) { |
||||
if (tree->refs[i] == ref) { |
||||
count += ug_tree_prune(tree, i); |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// find the size of the subtree starting from ref
|
||||
int ug_tree_subtree_size(UgTree *tree, int ref) |
||||
{ |
||||
if (tree == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!IS_VALID_REF(tree, ref)) { |
||||
return -1; |
||||
} |
||||
|
||||
if (!REF_IS_PRESENT(tree, ref)) { |
||||
return 0; |
||||
} |
||||
|
||||
int count = 1; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
// only root has the reference to itself
|
||||
if (tree->refs[i] == ref && ref != i) { |
||||
count += ug_tree_subtree_size(tree, i); |
||||
} |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
// iterate through the first level children, use a cursor like strtok_r
|
||||
int ug_tree_children_it(UgTree *tree, int parent, int *cursor) |
||||
{ |
||||
if (tree == NULL || cursor == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
// if the cursor is out of bounds then we are done for sure
|
||||
if (!IS_VALID_REF(tree, *cursor)) { |
||||
return -1; |
||||
} |
||||
|
||||
// same for the parent, if it's invalid it can't have children
|
||||
if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { |
||||
return -1; |
||||
} |
||||
|
||||
// find the first child, update the cursor and return the ref
|
||||
for (int i = *cursor; i < tree->size; i++) { |
||||
if (tree->refs[i] == parent) { |
||||
*cursor = i + 1; |
||||
return i; |
||||
} |
||||
} |
||||
|
||||
// if no children are found return -1
|
||||
*cursor = -1; |
||||
return -1; |
||||
} |
||||
|
||||
/* iterates trough every leaf of the subtree in the following manner
|
||||
* node [x], x: visit order |
||||
* [0] |
||||
* / | \
|
||||
* / [2] [3] |
||||
* [1] | |
||||
* / \ [6] |
||||
* [4] [5] |
||||
*/ |
||||
int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) |
||||
{ |
||||
if (tree == NULL || cursor == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
int *queue = tree->ordered_refs; |
||||
|
||||
// TODO: this could also be done when adding or removing elements
|
||||
// first call, create a ref array ordered like we desire
|
||||
if (*cursor == -1) { |
||||
*cursor = 0; |
||||
for (int i = 0; i < tree->size; i++) { |
||||
queue[i] = -1; |
||||
} |
||||
|
||||
// iterate through the queue appending found children
|
||||
int pos = 0, off = 0; |
||||
do { |
||||
// printf ("ref=%d\n", ref);
|
||||
|
||||
for (int i = 0; i < tree->size; i++) { |
||||
if (tree->refs[i] == ref) { |
||||
queue[pos++] = i; |
||||
} |
||||
} |
||||
|
||||
for (; ref == queue[off] && off < tree->size; off++) |
||||
; |
||||
ref = queue[off]; |
||||
|
||||
} while (IS_VALID_REF(tree, ref)); |
||||
// This line is why tree->ordered_refs has to be size+1
|
||||
queue[off + 1] = -1; |
||||
} |
||||
|
||||
// PRINT_ARR(queue, tree->size);
|
||||
// return -1;
|
||||
|
||||
// on successive calls just iterate through the queue until we find an
|
||||
// invalid ref, if the user set the cursor to -1 it means it has found what
|
||||
// he needed, so free
|
||||
if (*cursor < 0) { |
||||
return -1; |
||||
} else if (IS_VALID_REF(tree, *cursor)) { |
||||
return queue[(*cursor)++]; |
||||
} |
||||
|
||||
return -1; |
||||
} |
||||
|
||||
int ug_tree_parentof(UgTree *tree, int node) |
||||
{ |
||||
if (tree == NULL || !IS_VALID_REF(tree, node) || |
||||
!REF_IS_PRESENT(tree, node)) { |
||||
return -1; |
||||
} |
||||
return tree->refs[node]; |
||||
} |
||||
|
||||
UgId ug_tree_get(UgTree *tree, int node) |
||||
{ |
||||
if (tree == NULL || !IS_VALID_REF(tree, node)) { |
||||
return 0; |
||||
} |
||||
return tree->vector[node]; |
||||
} |
Loading…
Reference in new issue