faster text conversion to hex; guid generation now dealt with with -u.random_fit and blob formation; mersenne twister used for -u.random functions

This commit is contained in:
2025-06-08 13:42:20 -05:00
parent 3622a5ec58
commit a274fb174f
13 changed files with 269 additions and 138 deletions

View File

@@ -295,7 +295,7 @@ src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c'
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c'
]
# quirc src
src += [

View File

@@ -339,12 +339,16 @@ stone.p = function(object)
}
*/
var util = use('util')
var crypto = use('crypto')
function guid(bits = 256)
{
var guid = new blob(bits, hidden.randi)
stone(guid)
return text(guid,'h')
}
var HEADER = Symbol()
function create_actor(desc = {id:util.guid()}) {
function create_actor(desc = {id:guid()}) {
var actor = {}
actor[ACTORDATA] = desc
return actor
@@ -352,10 +356,10 @@ function create_actor(desc = {id:util.guid()}) {
var $_ = create_actor()
$_.random = crypto.random
$_.random = hidden.rand
$_.random[cell.DOC] = "returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5."
$_.random_fit = crypto.random_fit
$_.random_fit = hidden.randi
$_.clock = function(fn) {
actor_mod.clock(_ => {
@@ -484,7 +488,7 @@ $_.receiver[cell.DOC] = "registers a function that will receive all messages..."
$_.start = function start(cb, program, ...args) {
if (!program) return
var id = util.guid()
var id = guid()
if (args.length === 1 && Array.isArray(args[0]))
args = args[0]
@@ -636,7 +640,7 @@ globalThis.send = function send(actor, message, reply) {
}
if (reply) {
var id = util.guid()
var id = guid()
replies[id] = reply
$_.delay(_ => {
if (replies[id]) {
@@ -654,7 +658,7 @@ globalThis.send = function send(actor, message, reply) {
stone(send)
if (!cell.args.id) cell.id = util.guid()
if (!cell.args.id) cell.id = guid()
else cell.id = cell.args.id
$_[ACTORDATA].id = cell.id

View File

@@ -22,10 +22,6 @@ math.sigma[cell.DOC] = "Compute standard deviation of an array of numbers."
math.median[cell.DOC] = "Compute the median of an array of numbers."
math.length[cell.DOC] = "Return the length of a vector (i.e. sqrt of sum of squares)."
math.from_to[cell.DOC] = "Return an array of points from a start to an end, spaced out by a certain distance."
math.rand[cell.DOC] = "Return a random float in [0,1)."
math.randi[cell.DOC] = "Return a random 32-bit integer."
math.srand[cell.DOC] = "Seed the random number generator with the given integer, or with current time if none."
math.TAU = Math.PI * 2;
math.deg2rad = function (deg) { return deg * 0.0174533; };

View File

@@ -5,6 +5,8 @@
var blob = use('blob')
var utf8 = use('utf8')
var that = this
// Convert number to string with given radix
function to_radix(num, radix) {
if (radix < 2 || radix > 36) return null;
@@ -98,54 +100,10 @@ function text() {
// Handle blob encoding styles
switch (style) {
case 'h': // hexadecimal
// Read 8 bits at a time for full bytes
var hex_digits = "0123456789ABCDEF";
for (var i = 0; i < bit_length; i += 8) {
var byte_val = 0;
for (var j = 0; j < 8 && i + j < bit_length; j++) {
var bit = arg.read_logical(i + j);
if (bit) byte_val |= (1 << j);
}
result += hex_digits[(byte_val >> 4) & 0xF];
result += hex_digits[byte_val & 0xF];
}
return result;
return that.blob_to_hex(arg);
case 't': // base32
var b32_digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = 0;
var value = 0;
// Read bits from LSB to MSB within each byte
for (var byte_idx = 0; byte_idx < Math.ceil(bit_length / 8); byte_idx++) {
for (var bit_in_byte = 0; bit_in_byte < 8 && byte_idx * 8 + bit_in_byte < bit_length; bit_in_byte++) {
var bit_pos = byte_idx * 8 + bit_in_byte;
var bit = arg.read_logical(bit_pos);
// Accumulate bits from MSB to LSB for base32
value = (value << 1) | (bit ? 1 : 0);
bits++;
if (bits === 5) {
result += b32_digits[value];
bits = 0;
value = 0;
}
}
}
// Handle remaining bits
if (bits > 0) {
value = value << (5 - bits);
result += b32_digits[value];
}
// Add padding to make length multiple of 8
while (result.length % 8 !== 0) {
result += '=';
}
return result;
return that.blob_to_base32(arg);
case 'b': // binary
for (var i = 0; i < bit_length; i++) {

View File

@@ -21,8 +21,8 @@ typedef struct letter {
};
} letter;
#define STATE_VECTOR_LENGTH 624
#define STATE_VECTOR_M 397
#define STATE_VECTOR_LENGTH 312
#define STATE_VECTOR_M 156
#define ACTOR_IDLE 0 // Actor not doing anything
#define ACTOR_READY 1 // Actor ready for a turn
@@ -36,7 +36,7 @@ typedef JSValue (*MODULEFN)(JSContext *js);
extern int tracy_profiling_enabled;
typedef struct tagMTRand {
uint32_t mt[STATE_VECTOR_LENGTH];
uint64_t mt[STATE_VECTOR_LENGTH];
int32_t index;
} MTRand;

View File

@@ -56,6 +56,7 @@
#include "qjs_kim.h"
#include "qjs_utf8.h"
#include "qjs_fit.h"
#include "qjs_text.h"
#ifndef NSTEAM
#include "qjs_steam.h"
#endif
@@ -102,55 +103,59 @@ transform *js2transform(JSContext *js, JSValue v);
//#include <cblas.h>
#endif
// Random number generation constants
#define UPPER_MASK 0x80000000
#define LOWER_MASK 0x7fffffff
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
// Random number generation constants for MT19937-64
#define NN STATE_VECTOR_LENGTH
#define MM STATE_VECTOR_M
#define MATRIX_A 0xB5026F5AA96619E9ULL
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */
// Random number generation functions
void m_seedRand(MTRand* rand, uint32_t seed) {
/* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator
* from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer
* Programming," Vol. 2 (2nd Ed.) pp.102.
*/
rand->mt[0] = seed & 0xffffffff;
for(rand->index=1; rand->index<STATE_VECTOR_LENGTH; rand->index++) {
rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff;
void m_seedRand(MTRand* rand, uint64_t seed) {
rand->mt[0] = seed;
for(rand->index = 1; rand->index < NN; rand->index++) {
rand->mt[rand->index] = (6364136223846793005ULL * (rand->mt[rand->index-1] ^ (rand->mt[rand->index-1] >> 62)) + rand->index);
}
}
uint32_t genRandLong(MTRand* rand) {
uint32_t y;
static uint32_t mag[2] = {0x0, 0x9908b0df}; /* mag[x] = x * 0x9908b0df for x = 0,1 */
if(rand->index >= STATE_VECTOR_LENGTH || rand->index < 0) {
/* generate STATE_VECTOR_LENGTH words at a time */
int32_t kk;
if(rand->index >= STATE_VECTOR_LENGTH+1 || rand->index < 0) {
m_seedRand(rand, 4357);
int64_t genRandLong(MTRand* rand) {
int i;
uint64_t x;
static uint64_t mag01[2] = {0ULL, MATRIX_A};
if (rand->index >= NN) { /* generate NN words at one time */
/* if init_genrand64() has not been called, */
/* a default initial seed is used */
if (rand->index == NN+1)
m_seedRand(rand, 5489ULL);
for (i = 0; i < NN-MM; i++) {
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
rand->mt[i] = rand->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
}
for(kk=0; kk<STATE_VECTOR_LENGTH-STATE_VECTOR_M; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1];
for (; i < NN-1; i++) {
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
rand->mt[i] = rand->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
}
for(; kk<STATE_VECTOR_LENGTH-1; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+(STATE_VECTOR_M-STATE_VECTOR_LENGTH)] ^ (y >> 1) ^ mag[y & 0x1];
}
y = (rand->mt[STATE_VECTOR_LENGTH-1] & UPPER_MASK) | (rand->mt[0] & LOWER_MASK);
rand->mt[STATE_VECTOR_LENGTH-1] = rand->mt[STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1];
x = (rand->mt[NN-1] & UM) | (rand->mt[0] & LM);
rand->mt[NN-1] = rand->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
rand->index = 0;
}
y = rand->mt[rand->index++];
y ^= (y >> 11);
y ^= (y << 7) & TEMPERING_MASK_B;
y ^= (y << 15) & TEMPERING_MASK_C;
y ^= (y >> 18);
return y;
x = rand->mt[rand->index++];
x ^= (x >> 29) & 0x5555555555555555ULL;
x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
x ^= (x << 37) & 0xFFF7EEE000000000ULL;
x ^= (x >> 43);
return (int64_t)(x & 0x000FFFFFFFFFFFFFULL); /* return 52-bit value safe for JS */
}
double genRand(MTRand* rand) {
return((double)genRandLong(rand) / (uint32_t)0xffffffff);
/* generates a random number on [0,1)-real-interval */
return (genRandLong(rand) >> 11) * (1.0/9007199254740992.0);
}
double rand_range(JSContext *js, double min, double max)
@@ -947,15 +952,31 @@ static const JSCFunctionListEntry js_number_funcs[] = {
PROTO_FUNC_DEF(number, lerp, 2),
};
static uint32_t rng_state = 123456789;
static uint32_t xorshift32(){
uint32_t x = rng_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return rng_state = x;
}
JSC_CCALL(os_guid,
SDL_GUID guid;
randombytes(guid.data, 16);
uint8_t data[16];
for(int i = 0; i < 4; i++){
uint32_t v = xorshift32();
memcpy(&data[i*4], &v, 4);
}
char guid_str[33];
static const char hex[] = "0123456789abcdef";
char buf[32];
for(int i = 0; i < 16; i++){
uint8_t b = data[i];
buf[i*2 ] = hex[b >> 4];
buf[i*2 + 1] = hex[b & 0x0f];
}
SDL_GUIDToString(guid, guid_str, 33);
return JS_NewString(js,guid_str);
return JS_NewStringLen(js, buf, 32);
)
JSC_SCALL(console_print, printf("%s", str); )
@@ -1197,6 +1218,21 @@ JSC_CCALL(os_make_font,
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
JSC_CCALL(os_rand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRand(mrand));
)
JSC_CCALL(os_randi,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return JS_NewInt64(js, genRandLong(mrand));
)
JSC_CCALL(os_srand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
m_seedRand(mrand, js2number(js,argv[0]));
)
JSValue make_color_buffer(JSContext *js, colorf c, int verts)
{
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
@@ -1561,6 +1597,7 @@ void ffi_load(JSContext *js)
arrput(rt->module_registry, MISTLINE(kim));
arrput(rt->module_registry, MISTLINE(utf8));
arrput(rt->module_registry, MISTLINE(fit));
arrput(rt->module_registry, MISTLINE(text));
arrput(rt->module_registry, MISTLINE(wota));
arrput(rt->module_registry, MISTLINE(nota));
@@ -1623,6 +1660,9 @@ void ffi_load(JSContext *js)
// Add functions that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));
JS_SetPropertyStr(js, hidden_fn, "use_embed", JS_NewCFunction(js, js_os_use_embed, "use_embed", 1));
JS_SetPropertyStr(js, hidden_fn, "rand", JS_NewCFunction(js, js_os_rand, "rand", 0));
JS_SetPropertyStr(js, hidden_fn, "randi", JS_NewCFunction(js, js_os_randi, "randi", 0));
JS_SetPropertyStr(js, hidden_fn, "srand", JS_NewCFunction(js, js_os_srand, "srand", 1));
const char actorsym_script[] = "var sym = Symbol(`actordata`); sym;";

View File

@@ -42,8 +42,8 @@ typedef struct tagMTRand MTRand;
// Random number generation functions
double genRand(MTRand *mrand);
uint32_t genRandLong(MTRand *mrand);
void m_seedRand(MTRand *mrand, uint32_t seed);
int64_t genRandLong(MTRand *mrand);
void m_seedRand(MTRand *mrand, uint64_t seed);
double rand_range(JSContext *js, double min, double max);
// Common data structures

View File

@@ -50,33 +50,44 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
int is_one = JS_ToBool(ctx, argv[1]);
bd = blob_new_with_fill((size_t)length_bits, is_one);
} else if (JS_IsFunction(ctx, argv[1])) {
/* Random function provided call it for each bit */
/* Random function provided call it and use up to 56 bits at a time */
size_t bytes = (length_bits + 7) / 8;
bd = blob_new((size_t)length_bits);
if (bd) {
bd->length = length_bits;
/* Ensure the backing storage starts out zeroed */
memset(bd->data, 0, bytes);
for (size_t i = 0; i < length_bits; i++) {
size_t bits_written = 0;
while (bits_written < length_bits) {
JSValue randval = JS_Call(ctx, argv[1], JS_UNDEFINED, 0, NULL);
if (JS_IsException(randval)) {
blob_destroy(bd);
return JS_EXCEPTION;
}
int32_t fitval;
JS_ToInt32(ctx, &fitval, randval);
int64_t fitval;
JS_ToInt64(ctx, &fitval, randval);
JS_FreeValue(ctx, randval);
/* Compute which byte and which bit within that byte to set/clear */
size_t byte_idx = i / 8;
size_t bit_idx = i % 8;
/* Extract up to 56 bits from the random value */
size_t bits_to_use = length_bits - bits_written;
if (bits_to_use > 52) bits_to_use = 52;
if (fitval & 1)
/* Write bits from the random value */
for (size_t j = 0; j < bits_to_use; j++) {
size_t bit_pos = bits_written + j;
size_t byte_idx = bit_pos / 8;
size_t bit_idx = bit_pos % 8;
if (fitval & (1LL << j))
bd->data[byte_idx] |= (uint8_t)(1 << bit_idx);
else
bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx);
}
bits_written += bits_to_use;
}
}
} else {
return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function");

View File

@@ -231,20 +231,6 @@ JSC_CCALL(math_from_to,
return jsarr;
)
JSC_CCALL(math_rand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRand(mrand));
)
JSC_CCALL(math_randi,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRandLong(mrand));
)
JSC_CCALL(math_srand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
m_seedRand(mrand, js2number(js,argv[0]));
)
JSC_CCALL(math_dot,
size_t alen, blen;
@@ -497,9 +483,6 @@ static const JSCFunctionListEntry js_math_funcs[] = {
MIST_FUNC_DEF(math, median, 1),
MIST_FUNC_DEF(math, length, 1),
MIST_FUNC_DEF(math, from_to, 5),
MIST_FUNC_DEF(math, rand, 0),
MIST_FUNC_DEF(math, randi, 0),
MIST_FUNC_DEF(math, srand,0),
};
JSValue js_math_use(JSContext *js) {

94
source/qjs_text.c Normal file
View File

@@ -0,0 +1,94 @@
#include "qjs_text.h"
#include "qjs_blob.h"
#include "blob.h"
#include "jsffi.h"
#include <string.h>
#include <stdlib.h>
JSC_CCALL(text_blob_to_hex,
size_t blob_len;
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
uint8_t *bytes = (uint8_t *)blob_data;
// Hex encoding: each byte becomes 2 hex characters
size_t hex_len = blob_len * 2;
char *hex_str = malloc(hex_len + 1);
static const char hex_digits[] = "0123456789ABCDEF";
for (size_t i = 0; i < blob_len; i++) {
hex_str[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF];
hex_str[i * 2 + 1] = hex_digits[bytes[i] & 0xF];
}
hex_str[hex_len] = '\0';
ret = JS_NewString(js, hex_str);
free(hex_str);
)
JSC_CCALL(text_blob_to_base32,
size_t blob_len;
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
uint8_t *bytes = (uint8_t *)blob_data;
static const char b32_digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Calculate output length: each 5 bytes becomes 8 base32 chars
size_t groups = (blob_len + 4) / 5;
size_t b32_len = groups * 8;
char *b32_str = malloc(b32_len + 1);
size_t in_idx = 0;
size_t out_idx = 0;
while (in_idx < blob_len) {
// Process 5 bytes (40 bits) at a time to produce 8 base32 chars
uint64_t buf = 0;
int bytes_read = 0;
// Read up to 5 bytes into buffer
for (int i = 0; i < 5 && in_idx < blob_len; i++) {
buf = (buf << 8) | bytes[in_idx++];
bytes_read++;
}
// Pad with zeros if we read fewer than 5 bytes
buf = buf << (8 * (5 - bytes_read));
// Extract 8 groups of 5 bits from the 40-bit buffer
for (int i = 7; i >= 0; i--) {
b32_str[out_idx + i] = b32_digits[buf & 0x1F];
buf >>= 5;
}
out_idx += 8;
}
// Replace trailing chars with padding if needed
int padding = (5 - (blob_len % 5)) % 5;
if (padding > 0) {
static const int pad_chars[] = {0, 6, 4, 3, 1};
for (int i = 0; i < pad_chars[padding]; i++) {
b32_str[b32_len - 1 - i] = '=';
}
}
b32_str[b32_len] = '\0';
ret = JS_NewString(js, b32_str);
free(b32_str);
)
static const JSCFunctionListEntry js_text_funcs[] = {
MIST_FUNC_DEF(text, blob_to_hex, 1),
MIST_FUNC_DEF(text, blob_to_base32, 1),
};
JSValue js_text_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs));
return mod;
}

8
source/qjs_text.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_TEXT_H
#define QJS_TEXT_H
#include <quickjs.h>
JSValue js_text_use(JSContext *js);
#endif

14
tests/guid.ce Normal file
View File

@@ -0,0 +1,14 @@
var blob = use('blob')
var time = use('time')
var st = time.number()
var guid = new blob(256, $_.random_fit)
stone(guid)
var btime = time.number()-st
st = time.number()
guid = text(guid,'h')
st = time.number()-st
log.console(`took ${btime*1000000} us to make blob; took ${st*1000000} us to make it text`)
log.console(guid.toLowerCase())
log.console(guid.length)
$_.stop()

23
tests/text_test.ce Normal file
View File

@@ -0,0 +1,23 @@
// Test text module
var text = use('text')
var blob = use('blob')
// Create a test blob with some data
var b = new blob()
b.write_text("Hello")
b.stone()
log.console("Original blob content (as text):", text(b))
log.console("Hex encoding:", text(b, 'h'))
log.console("Base32 encoding:", text(b, 't'))
// Test with binary data
var b2 = new blob()
b2.write_fit(255, 8)
b2.write_fit(170, 8) // 10101010 in binary
b2.write_fit(15, 8) // 00001111 in binary
b2.stone()
log.console("\nBinary data tests:")
log.console("Hex encoding:", text(b2, 'h'))
log.console("Base32 encoding:", text(b2, 't'))