Merge branch 'temp-branch'
This commit is contained in:
@@ -42,3 +42,7 @@ Assume memory allocation will eventually fail. Assume the filesystem will eventu
|
|||||||
- Avoid separate functions for each method handler. Inline them into the single handler that handles dispatch. This not only net-shrinks the codebase,
|
- Avoid separate functions for each method handler. Inline them into the single handler that handles dispatch. This not only net-shrinks the codebase,
|
||||||
but also means that navigating by searching for `== NN_<COMPONENT>_<REQUEST>)` (in NCL) or `== NN_<COMPONENT>NUM_<METHOD>)` (in NN) will show the
|
but also means that navigating by searching for `== NN_<COMPONENT>_<REQUEST>)` (in NCL) or `== NN_<COMPONENT>NUM_<METHOD>)` (in NN) will show the
|
||||||
dispatch logic and implementation.
|
dispatch logic and implementation.
|
||||||
|
- Properties which are always equal to a constant from the config, or simply has no dependency on state, must not be requests, and instead handled by
|
||||||
|
the component class directly.
|
||||||
|
- All loops inside methods must be bound by something controlled by either the config or a compile-time constant, to prevent denial of service attacks on
|
||||||
|
the worker thread.
|
||||||
|
|||||||
212
src/glyphcache.c
Normal file
212
src/glyphcache.c
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#include <raylib.h>
|
||||||
|
#include "glyphcache.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dynamic glyph cache for raylib
|
||||||
|
* (C) - "Raylib's text renderer handles unicode like garbage"
|
||||||
|
* Problem: raylib's LoadFont only loads ~95 ASCII glyphs.
|
||||||
|
* LoadFontEx can load arbitrary codepoints, but you must know
|
||||||
|
* them upfront, and rebuilding every frame is expensive.
|
||||||
|
*
|
||||||
|
* So we should lazily collect codepoints the screen actually uses,
|
||||||
|
* rebuild the font atlas only when new ones appear.
|
||||||
|
* Typically this stabilises after the first few frames.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define GC_INITIAL_CAP 4096
|
||||||
|
#define GC_BUCKET_COUNT 8192 // must be power of 2
|
||||||
|
#define GC_BUCKET_MASK (GC_BUCKET_COUNT - 1)
|
||||||
|
|
||||||
|
// Codepoint set (open-addressing hash set)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int *slots; // 0 = empty sentinel (U+0000 never needed)
|
||||||
|
size_t count;
|
||||||
|
size_t cap; // always a power of 2
|
||||||
|
} CpSet;
|
||||||
|
|
||||||
|
static void cpset_init(CpSet *s) {
|
||||||
|
s->cap = GC_BUCKET_COUNT;
|
||||||
|
s->count = 0;
|
||||||
|
s->slots = calloc(s->cap, sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cpset_free(CpSet *s) {
|
||||||
|
free(s->slots);
|
||||||
|
s->slots = NULL;
|
||||||
|
s->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cpset_contains(const CpSet *s, int cp) {
|
||||||
|
size_t idx = (unsigned)cp & (s->cap - 1);
|
||||||
|
for(size_t i = 0; i < s->cap; i++) {
|
||||||
|
size_t j = (idx + i) & (s->cap - 1);
|
||||||
|
if(s->slots[j] == 0) return false;
|
||||||
|
if(s->slots[j] == cp) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cpset_grow(CpSet *s);
|
||||||
|
|
||||||
|
// Returns true if the codepoint was newly inserted.
|
||||||
|
static bool cpset_insert(CpSet *s, int cp) {
|
||||||
|
if(cp == 0) return false; // sentinel
|
||||||
|
if(cpset_contains(s, cp)) return false;
|
||||||
|
|
||||||
|
if(s->count * 4 >= s->cap * 3) cpset_grow(s);
|
||||||
|
|
||||||
|
size_t idx = (unsigned)cp & (s->cap - 1);
|
||||||
|
for(size_t i = 0; i < s->cap; i++) {
|
||||||
|
size_t j = (idx + i) & (s->cap - 1);
|
||||||
|
if(s->slots[j] == 0) {
|
||||||
|
s->slots[j] = cp;
|
||||||
|
s->count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // should never happen after grow
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cpset_grow(CpSet *s) {
|
||||||
|
size_t oldCap = s->cap;
|
||||||
|
int *old = s->slots;
|
||||||
|
|
||||||
|
s->cap *= 2;
|
||||||
|
s->slots = calloc(s->cap, sizeof(int));
|
||||||
|
s->count = 0;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < oldCap; i++)
|
||||||
|
if(old[i] != 0) cpset_insert(s, old[i]);
|
||||||
|
|
||||||
|
free(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill dst (must hold at least s->count ints).
|
||||||
|
static void cpset_collect(const CpSet *s, int *dst) {
|
||||||
|
size_t n = 0;
|
||||||
|
for(size_t i = 0; i < s->cap; i++)
|
||||||
|
if(s->slots[i] != 0) dst[n++] = s->slots[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glyph cache
|
||||||
|
|
||||||
|
struct ncl_GlyphCache {
|
||||||
|
char *fontPath;
|
||||||
|
int fontSize;
|
||||||
|
Font font;
|
||||||
|
bool fontLoaded;
|
||||||
|
bool dirty; // new codepoints since last rebuild
|
||||||
|
CpSet known; // all codepoints we have glyphs for
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pre-seed the most common ranges so the first frame is not barren.
|
||||||
|
static void gc_seed(ncl_GlyphCache *gc) {
|
||||||
|
// ASCII printable
|
||||||
|
for(int i = 0x0020; i <= 0x007E; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Latin-1 Supplement
|
||||||
|
for(int i = 0x00A0; i <= 0x00FF; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Cyrillic (common)
|
||||||
|
for(int i = 0x0400; i <= 0x04FF; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// General punctuation
|
||||||
|
for(int i = 0x2010; i <= 0x2027; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Box drawing
|
||||||
|
for(int i = 0x2500; i <= 0x257F; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Block elements
|
||||||
|
for(int i = 0x2580; i <= 0x259F; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Geometric shapes (partial)
|
||||||
|
for(int i = 0x25A0; i <= 0x25FF; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Braille patterns
|
||||||
|
for(int i = 0x2800; i <= 0x28FF; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
// Powerline / private use (common in OC themes)
|
||||||
|
for(int i = 0xE000; i <= 0xE0FF; i++) cpset_insert(&gc->known, i);
|
||||||
|
|
||||||
|
gc->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gc_rebuild(ncl_GlyphCache *gc) {
|
||||||
|
if(gc->fontLoaded) UnloadFont(gc->font);
|
||||||
|
|
||||||
|
size_t n = gc->known.count;
|
||||||
|
if(n == 0) { gc->fontLoaded = false; gc->dirty = false; return; }
|
||||||
|
|
||||||
|
int *cps = malloc(sizeof(int) * n);
|
||||||
|
cpset_collect(&gc->known, cps);
|
||||||
|
|
||||||
|
gc->font = LoadFontEx(gc->fontPath, gc->fontSize, cps, (int)n);
|
||||||
|
gc->fontLoaded = true;
|
||||||
|
gc->dirty = false;
|
||||||
|
|
||||||
|
// Let raylib use bilinear for scaled glyphs, nearest for 1:1.
|
||||||
|
SetTextureFilter(gc->font.texture, TEXTURE_FILTER_POINT);
|
||||||
|
|
||||||
|
free(cps);
|
||||||
|
|
||||||
|
fprintf(stderr, "[glyphcache] rebuilt atlas: %zu glyphs, tex %dx%d\n",
|
||||||
|
n, gc->font.texture.width, gc->font.texture.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
|
||||||
|
ncl_GlyphCache *ncl_createGlyphCache(const char *fontPath, int fontSize) {
|
||||||
|
ncl_GlyphCache *gc = calloc(1, sizeof(*gc));
|
||||||
|
gc->fontPath = strdup(fontPath);
|
||||||
|
gc->fontSize = fontSize;
|
||||||
|
gc->fontLoaded = false;
|
||||||
|
gc->dirty = false;
|
||||||
|
cpset_init(&gc->known);
|
||||||
|
gc_seed(gc);
|
||||||
|
gc_rebuild(gc);
|
||||||
|
return gc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncl_destroyGlyphCache(ncl_GlyphCache *gc) {
|
||||||
|
if(!gc) return;
|
||||||
|
if(gc->fontLoaded) UnloadFont(gc->font);
|
||||||
|
cpset_free(&gc->known);
|
||||||
|
free(gc->fontPath);
|
||||||
|
free(gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Font ncl_getFont(ncl_GlyphCache *gc) {
|
||||||
|
return gc->font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncl_flushGlyphs(ncl_GlyphCache *gc) {
|
||||||
|
if(gc->dirty) gc_rebuild(gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncl_needGlyph(ncl_GlyphCache *gc, nn_codepoint cp) {
|
||||||
|
if(cp == 0) return;
|
||||||
|
if(cpset_insert(&gc->known, (int)cp))
|
||||||
|
gc->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ncl_drawGlyph(ncl_GlyphCache *gc, nn_codepoint cp,
|
||||||
|
Vector2 pos, float size, Color tint)
|
||||||
|
{
|
||||||
|
ncl_needGlyph(gc, cp);
|
||||||
|
DrawTextCodepoint(gc->font, (int)cp, pos, size, tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ncl_cellWidth(ncl_GlyphCache *gc) {
|
||||||
|
// Measure 'A' as the reference cell.
|
||||||
|
if(!gc->fontLoaded) return 8;
|
||||||
|
return MeasureTextEx(gc->font, "A", (float)gc->fontSize, 0).x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ncl_cellHeight(ncl_GlyphCache *gc) {
|
||||||
|
return gc->fontSize;
|
||||||
|
}
|
||||||
19
src/glyphcache.h
Normal file
19
src/glyphcache.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef NCL_GLYPHCACHE_H
|
||||||
|
#define NCL_GLYPHCACHE_H
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
#include "neonucleus.h"
|
||||||
|
|
||||||
|
typedef struct ncl_GlyphCache ncl_GlyphCache;
|
||||||
|
|
||||||
|
ncl_GlyphCache *ncl_createGlyphCache(const char *fontPath, int fontSize);
|
||||||
|
void ncl_destroyGlyphCache(ncl_GlyphCache *gc);
|
||||||
|
Font ncl_getFont(ncl_GlyphCache *gc);
|
||||||
|
void ncl_flushGlyphs(ncl_GlyphCache *gc);
|
||||||
|
void ncl_needGlyph(ncl_GlyphCache *gc, nn_codepoint cp);
|
||||||
|
void ncl_drawGlyph(ncl_GlyphCache *gc, nn_codepoint cp,
|
||||||
|
Vector2 pos, float size, Color tint);
|
||||||
|
int ncl_cellWidth(ncl_GlyphCache *gc);
|
||||||
|
int ncl_cellHeight(ncl_GlyphCache *gc);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -3215,6 +3215,9 @@ typedef enum nn_EENum {
|
|||||||
NN_EENUM_SETDATA,
|
NN_EENUM_SETDATA,
|
||||||
NN_EENUM_SETLABEL,
|
NN_EENUM_SETLABEL,
|
||||||
NN_EENUM_SETARCH,
|
NN_EENUM_SETARCH,
|
||||||
|
NN_EENUM_ISRO,
|
||||||
|
NN_EENUM_GETCHKSUM,
|
||||||
|
NN_EENUM_MKRO,
|
||||||
|
|
||||||
NN_EENUM_COUNT,
|
NN_EENUM_COUNT,
|
||||||
} nn_EENum;
|
} nn_EENum;
|
||||||
@@ -3359,6 +3362,9 @@ nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const
|
|||||||
[NN_EENUM_SETDATA] = {"setData", "function(data: string) - Set the data on the EEPROM", NN_INDIRECT},
|
[NN_EENUM_SETDATA] = {"setData", "function(data: string) - Set the data on the EEPROM", NN_INDIRECT},
|
||||||
[NN_EENUM_SETLABEL] = {"setLabel", "function(label?: string) - Set the label", NN_INDIRECT},
|
[NN_EENUM_SETLABEL] = {"setLabel", "function(label?: string) - Set the label", NN_INDIRECT},
|
||||||
[NN_EENUM_SETARCH] = {"setArchitecture", "function(arch?: string) - Set the desired architecture", NN_INDIRECT},
|
[NN_EENUM_SETARCH] = {"setArchitecture", "function(arch?: string) - Set the desired architecture", NN_INDIRECT},
|
||||||
|
[NN_EENUM_ISRO] = {"isReadonly", "function(): boolean - Returns whether the EEPROM is read-only.", NN_DIRECT},
|
||||||
|
[NN_EENUM_GETCHKSUM] = {"getChecksum", "function(): string - Returns a checksum of the EEPROM code.", NN_DIRECT},
|
||||||
|
[NN_EENUM_MKRO] = {"makeReadonly", "function(checksum: string): boolean - Make the EEPROM read-only if checksum passes.", NN_INDIRECT},
|
||||||
};
|
};
|
||||||
nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_EENUM_COUNT);
|
nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_EENUM_COUNT);
|
||||||
if(e) {
|
if(e) {
|
||||||
@@ -4431,14 +4437,16 @@ static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) {
|
|||||||
g.resolution.height = nn_tointeger(C, 1);
|
g.resolution.height = nn_tointeger(C, 1);
|
||||||
e = cls->handler(&g);
|
e = cls->handler(&g);
|
||||||
if(e) return e;
|
if(e) return e;
|
||||||
// push screen_resized via getScreen
|
// signal is best-effort, resolution is
|
||||||
|
// already changed at this point
|
||||||
nn_GPURequest s = g;
|
nn_GPURequest s = g;
|
||||||
s.action = NN_GPU_GETSCREEN;
|
s.action = NN_GPU_GETSCREEN;
|
||||||
s.screenAddr[0] = '\0';
|
s.screenAddr[0] = '\0';
|
||||||
cls->handler(&s);
|
if(cls->handler(&s) == NN_OK
|
||||||
if(s.screenAddr[0] != '\0') {
|
&& s.screenAddr[0] != '\0') {
|
||||||
nn_pushScreenResized(C, s.screenAddr,
|
nn_pushScreenResized(C, s.screenAddr,
|
||||||
g.resolution.width, g.resolution.height);
|
g.resolution.width,
|
||||||
|
g.resolution.height);
|
||||||
}
|
}
|
||||||
req->returnCount = 1;
|
req->returnCount = 1;
|
||||||
return nn_pushbool(C, true);
|
return nn_pushbool(C, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user