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 |
*.o |
||||||
test/test |
build/* |
||||||
**/compile_commands.json |
|
||||||
**/.cache |
|
||||||
test |
|
||||||
raylib/* |
|
||||||
|
@ -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