ported rewrite2 to c3
This commit is contained in:
parent
2dcc1b582c
commit
39e78ea078
.clang-format.gitignore.gitmodulesLICENSEMakefileREADME.mdcache.ccompile_flags.txt
docs
fm
get_raylib.shlib
project.jsonresources
scripts
src
stuff
test
timer.ctimer.hugui.cugui.hvectree.c@ -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
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,8 +1,2 @@
|
||||
microgui
|
||||
ugui
|
||||
*.o
|
||||
test/test
|
||||
**/compile_commands.json
|
||||
**/.cache
|
||||
test
|
||||
raylib/*
|
||||
build/*
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "lib/raylib.c3l"]
|
||||
path = lib/raylib.c3l
|
||||
url = https://github.com/NexushasTaken/raylib.c3l
|
15
Makefile
15
Makefile
@ -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
|
211
cache.c
211
cache.c
@ -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
|
0
docs/.gitkeep
Normal file
0
docs/.gitkeep
Normal file
193
fm/libconf.c
193
fm/libconf.c
@ -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
lib/.gitkeep
Normal file
0
lib/.gitkeep
Normal file
1
lib/raylib.c3l
Submodule
1
lib/raylib.c3l
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c7ebe054ce16136c1128fab54fcce4921044293e
|
56
project.json
Normal file
56
project.json
Normal file
@ -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
resources/.gitkeep
Normal file
0
resources/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
133
src/cache.c3
Normal file
133
src/cache.c3
Normal file
@ -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;
|
||||
}
|
||||
}
|
49
src/fifo.c3
Normal file
49
src/fifo.c3
Normal file
@ -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;
|
||||
}
|
196
src/main.c3
Normal file
196
src/main.c3
Normal file
@ -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");
|
||||
}
|
184
src/ugui_data.c3
Normal file
184
src/ugui_data.c3
Normal file
@ -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);
|
||||
}
|
244
src/ugui_impl.c3
Normal file
244
src/ugui_impl.c3
Normal file
@ -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;
|
||||
}
|
78
src/ugui_input.c3
Normal file
78
src/ugui_input.c3
Normal file
@ -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;
|
||||
}
|
153
src/ugui_layout.c3
Normal file
153
src/ugui_layout.c3
Normal file
@ -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;
|
||||
}
|
333
src/vtree.c3
Normal file
333
src/vtree.c3
Normal file
@ -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
|
214
stuff/main.c
214
stuff/main.c
@ -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;
|
||||
}
|
348
stuff/vectree.h
348
stuff/vectree.h
@ -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
test/.gitkeep
Normal file
0
test/.gitkeep
Normal file
14
test/test_bitsruct.c3
Normal file
14
test/test_bitsruct.c3
Normal file
@ -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;
|
||||
}
|
26
test/test_union.c3
Normal file
26
test/test_union.c3
Normal file
@ -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;
|
||||
}
|
7
test/test_vtree.c3
Normal file
7
test/test_vtree.c3
Normal file
@ -0,0 +1,7 @@
|
||||
import std::io;
|
||||
import vtree;
|
||||
|
||||
fn int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
69
timer.c
69
timer.c
@ -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);
|
||||
}
|
16
timer.h
16
timer.h
@ -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
|
105
ugui.h
105
ugui.h
@ -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
|
355
vectree.c
355
vectree.c
@ -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
Block a user