Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd27e0783 | ||
|
|
6eb33b8e48 | ||
|
|
3d5f345236 | ||
|
|
4689ea1167 | ||
|
|
15d85096a2 | ||
|
|
f2c2ecf692 | ||
|
|
458215f838 | ||
|
|
7f002e306d | ||
|
|
91a3fef065 | ||
|
|
843b4bd8a8 | ||
|
|
41fdf49df5 | ||
|
|
c9adbed3ff | ||
|
|
3459d85a82 | ||
|
|
9ecdaae7a7 | ||
|
|
43b55b29f3 | ||
|
|
a88cee7fae | ||
|
|
8b3f5476a9 | ||
|
|
233f59e04a | ||
|
|
7b16259c00 | ||
|
|
43baa23dfe | ||
|
|
a9ebea9f26 | ||
|
|
19b729afbf | ||
|
|
47729c225f | ||
|
|
ea6cf5db49 | ||
|
|
794baf8598 | ||
|
|
a551368681 | ||
|
|
c20ca8c937 | ||
|
|
0217cf0da6 | ||
|
|
6bd7251933 | ||
|
|
6dc8d97001 | ||
|
|
9c0565d34f | ||
|
|
0702e3495d | ||
|
|
108c39d22d | ||
|
|
946ebe5cd7 | ||
|
|
fa12281ab9 | ||
|
|
38a52fcb73 | ||
|
|
95a95e55e3 | ||
|
|
fc978a5766 | ||
|
|
9082ee2c47 | ||
|
|
b8ad8431f4 | ||
|
|
1c2b8228fe | ||
|
|
a274fb174f | ||
|
|
3622a5ec58 | ||
|
|
8a5f8a4d74 | ||
|
|
c1d341eecd | ||
|
|
3176e6775d | ||
|
|
34dcd0a235 | ||
|
|
cbda7dfbc9 | ||
|
|
d039e2cfe6 | ||
|
|
c02bd06ec0 | ||
|
|
efa63771e6 | ||
|
|
9f6d27fb3c | ||
|
|
1a61ae6f77 | ||
|
|
83c816fd0e | ||
|
|
adbaa92dd5 | ||
|
|
580df9f233 | ||
|
|
d5d17560f9 | ||
|
|
cd05ab97b5 | ||
|
|
4eecbd692b | ||
|
|
72beed7177 | ||
|
|
e0595de71a | ||
|
|
6687008d1a | ||
|
|
5b9f1b8f51 | ||
|
|
c570de7f41 | ||
|
|
d0138a6c23 | ||
|
|
29aa25e866 | ||
|
|
ef28be93db | ||
|
|
0d7be6a94e | ||
|
|
4fe78c4a63 | ||
|
|
b52edb2746 | ||
|
|
79d5412fe6 | ||
|
|
fcec2cd1dc | ||
|
|
2038ce15a7 | ||
|
|
08557011cb | ||
|
|
3e87bfd6cc | ||
|
|
ef86dd3ecf | ||
|
|
c887bcf7b9 | ||
|
|
709f2459e4 |
@@ -1,8 +1,17 @@
|
|||||||
|
sdl_video = "main"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
extramath = "https://gitea.pockle.world/john/extramath@master"
|
extramath = "https://gitea.pockle.world/john/extramath@master"
|
||||||
|
|
||||||
[system]
|
[system]
|
||||||
ar_timer = 60 # seconds before idle actor reclamation
|
ar_timer = 60
|
||||||
actor_memory = 0 # MB of memory an actor can use; 0 for unbounded
|
actor_memory = 0
|
||||||
net_service = 0.1 # seconds per net service pull
|
net_service = 0.1
|
||||||
reply_timeout = 60 # seconds to hold callback for reply messages; 0 for unbounded
|
reply_timeout = 60
|
||||||
|
actor_max = "10_000"
|
||||||
|
stack_max = 0
|
||||||
|
[actors]
|
||||||
|
[actors.prosperon/sdl_video]
|
||||||
|
main = true
|
||||||
|
[actors.prosperon/prosperon]
|
||||||
|
main = true
|
||||||
|
[actors.prosperon]
|
||||||
|
main = true
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ This is a game engine developed using a QuickJS fork as its scripting language.
|
|||||||
|
|
||||||
## Coding Practices
|
## Coding Practices
|
||||||
- Use K&R style C
|
- Use K&R style C
|
||||||
- Use as little whitespace as possible
|
|
||||||
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
|
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,5 +1,5 @@
|
|||||||
debug: FORCE
|
debug: FORCE
|
||||||
meson setup build_dbg -Dbuildtype=debug
|
meson setup build_dbg -Dbuildtype=debugoptimized
|
||||||
meson install --only-changed -C build_dbg
|
meson install --only-changed -C build_dbg
|
||||||
|
|
||||||
fast: FORCE
|
fast: FORCE
|
||||||
|
|||||||
43
benchmarks/binarytree.ce
Normal file
43
benchmarks/binarytree.ce
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
function mainThread() {
|
||||||
|
var maxDepth = Math.max(6, Number(arg[0] || 16));
|
||||||
|
|
||||||
|
var stretchDepth = maxDepth + 1;
|
||||||
|
var check = itemCheck(bottomUpTree(stretchDepth));
|
||||||
|
log.console(`stretch tree of depth ${stretchDepth}\t check: ${check}`);
|
||||||
|
|
||||||
|
var longLivedTree = bottomUpTree(maxDepth);
|
||||||
|
|
||||||
|
for (let depth = 4; depth <= maxDepth; depth += 2) {
|
||||||
|
var iterations = 1 << maxDepth - depth + 4;
|
||||||
|
work(iterations, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.console(`long lived tree of depth ${maxDepth}\t check: ${itemCheck(longLivedTree)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function work(iterations, depth) {
|
||||||
|
let check = 0;
|
||||||
|
for (let i = 0; i < iterations; i++)
|
||||||
|
check += itemCheck(bottomUpTree(depth));
|
||||||
|
log.console(`${iterations}\t trees of depth ${depth}\t check: ${check}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TreeNode(left, right) {
|
||||||
|
return {left, right};
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemCheck(node) {
|
||||||
|
if (node.left == null)
|
||||||
|
return 1;
|
||||||
|
return 1 + itemCheck(node.left) + itemCheck(node.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bottomUpTree(depth) {
|
||||||
|
return depth > 0
|
||||||
|
? new TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
|
||||||
|
: new TreeNode(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainThread()
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
24
benchmarks/eratosthenes.ce
Normal file
24
benchmarks/eratosthenes.ce
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
var blob = use('blob')
|
||||||
|
|
||||||
|
function eratosthenes (n) {
|
||||||
|
var sieve = new blob(n, true)
|
||||||
|
var sqrtN = Math.trunc(Math.sqrt(n));
|
||||||
|
|
||||||
|
for (i = 2; i <= sqrtN; i++)
|
||||||
|
if (sieve.read_logical(i))
|
||||||
|
for (j = i * i; j <= n; j += i)
|
||||||
|
sieve.write_bit(j, false);
|
||||||
|
|
||||||
|
return sieve;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sieve = eratosthenes(10000000);
|
||||||
|
stone(sieve)
|
||||||
|
|
||||||
|
var c = 0
|
||||||
|
for (var i = 0; i < sieve.length; i++)
|
||||||
|
if (sieve.read_logical(i)) c++
|
||||||
|
|
||||||
|
log.console(c)
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
58
benchmarks/fannkuch.ce
Normal file
58
benchmarks/fannkuch.ce
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
function fannkuch(n) {
|
||||||
|
var perm1 = [n]
|
||||||
|
for (let i = 0; i < n; i++) perm1[i] = i
|
||||||
|
var perm = [n]
|
||||||
|
var count = [n]
|
||||||
|
var f = 0, flips = 0, nperm = 0, checksum = 0
|
||||||
|
var i, k, r
|
||||||
|
|
||||||
|
r = n
|
||||||
|
while (r > 0) {
|
||||||
|
i = 0
|
||||||
|
while (r != 1) { count[r-1] = r; r -= 1 }
|
||||||
|
while (i < n) { perm[i] = perm1[i]; i += 1 }
|
||||||
|
|
||||||
|
// Count flips and update max and checksum
|
||||||
|
f = 0
|
||||||
|
k = perm[0]
|
||||||
|
while (k != 0) {
|
||||||
|
i = 0
|
||||||
|
while (2*i < k) {
|
||||||
|
let t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
k = perm[0]
|
||||||
|
f += 1
|
||||||
|
}
|
||||||
|
if (f > flips) flips = f
|
||||||
|
if ((nperm & 0x1) == 0) checksum += f; else checksum -= f
|
||||||
|
|
||||||
|
// Use incremental change to generate another permutation
|
||||||
|
var more = true
|
||||||
|
while (more) {
|
||||||
|
if (r == n) {
|
||||||
|
log.console( checksum )
|
||||||
|
return flips
|
||||||
|
}
|
||||||
|
let p0 = perm1[0]
|
||||||
|
i = 0
|
||||||
|
while (i < r) {
|
||||||
|
let j = i + 1
|
||||||
|
perm1[i] = perm1[j]
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
perm1[r] = p0
|
||||||
|
|
||||||
|
count[r] -= 1
|
||||||
|
if (count[r] > 0) more = false; else r += 1
|
||||||
|
}
|
||||||
|
nperm += 1
|
||||||
|
}
|
||||||
|
return flips;
|
||||||
|
}
|
||||||
|
|
||||||
|
var n = arg[0] || 10
|
||||||
|
|
||||||
|
log.console(`Pfannkuchen(${n}) = ${fannkuch(n)}`)
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
20
benchmarks/hyperfine_wota_nota_json.sh
Executable file
20
benchmarks/hyperfine_wota_nota_json.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run hyperfine with parameter lists
|
||||||
|
# This will create a cross-product of all libraries × all scenarios
|
||||||
|
hyperfine \
|
||||||
|
--warmup 3 \
|
||||||
|
--runs 20 \
|
||||||
|
-i \
|
||||||
|
--export-csv wota_vs_nota_vs_json.csv \
|
||||||
|
--export-json wota_vs_nota_vs_json.json \
|
||||||
|
--export-markdown wota_vs_nota_vs_json.md \
|
||||||
|
--parameter-list lib wota,nota,json \
|
||||||
|
--parameter-list scen empty,integers,floats,strings,objects,nested,large_array \
|
||||||
|
'cell benchmarks/wota_nota_json {lib} {scen}'
|
||||||
|
|
||||||
|
|
||||||
|
echo "Benchmark complete! Results saved to:"
|
||||||
|
echo " - wota_vs_nota_vs_json.csv"
|
||||||
|
echo " - wota_vs_nota_vs_json.json"
|
||||||
|
echo " - wota_vs_nota_vs_json.md"
|
||||||
395
benchmarks/js_perf.ce
Normal file
395
benchmarks/js_perf.ce
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
var time = use('time')
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// JavaScript Performance Benchmark Suite
|
||||||
|
// Tests core JS operations: property access, function calls, arithmetic, etc.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Test configurations
|
||||||
|
const iterations = {
|
||||||
|
simple: 10000000,
|
||||||
|
medium: 1000000,
|
||||||
|
complex: 100000
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utility: measureTime(fn) => how long fn() takes in seconds
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function measureTime(fn) {
|
||||||
|
var start = time.number();
|
||||||
|
fn();
|
||||||
|
var end = time.number();
|
||||||
|
return (end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: Property Access
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchPropertyAccess() {
|
||||||
|
var obj = {
|
||||||
|
a: 1, b: 2, c: 3, d: 4, e: 5,
|
||||||
|
nested: { x: 10, y: 20, z: 30 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var readTime = measureTime(function() {
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
sum += obj.a + obj.b + obj.c + obj.d + obj.e;
|
||||||
|
sum += obj.nested.x + obj.nested.y + obj.nested.z;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var writeTime = measureTime(function() {
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
obj.a = i;
|
||||||
|
obj.b = i + 1;
|
||||||
|
obj.c = i + 2;
|
||||||
|
obj.nested.x = i * 2;
|
||||||
|
obj.nested.y = i * 3;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { readTime: readTime, writeTime: writeTime };
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: Function Calls
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchFunctionCalls() {
|
||||||
|
function add(a, b) { return a + b; }
|
||||||
|
function multiply(a, b) { return a * b; }
|
||||||
|
function complexCalc(a, b, c) { return (a + b) * c / 2; }
|
||||||
|
|
||||||
|
var obj = {
|
||||||
|
method: function(x) { return x * 2; },
|
||||||
|
nested: {
|
||||||
|
deepMethod: function(x, y) { return x + y; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var simpleCallTime = measureTime(function() {
|
||||||
|
var result = 0;
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
result = add(i, 1);
|
||||||
|
result = multiply(result, 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var methodCallTime = measureTime(function() {
|
||||||
|
var result = 0;
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
result = obj.method(i);
|
||||||
|
result = obj.nested.deepMethod(result, i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var complexCallTime = measureTime(function() {
|
||||||
|
var result = 0;
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
result = complexCalc(i, i + 1, i + 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
simpleCallTime: simpleCallTime,
|
||||||
|
methodCallTime: methodCallTime,
|
||||||
|
complexCallTime: complexCallTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: Array Operations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchArrayOps() {
|
||||||
|
var pushTime = measureTime(function() {
|
||||||
|
var arr = [];
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
arr.push(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var arr = [];
|
||||||
|
for (var i = 0; i < 10000; i++) arr.push(i);
|
||||||
|
|
||||||
|
var accessTime = measureTime(function() {
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
sum += arr[i % 10000];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var iterateTime = measureTime(function() {
|
||||||
|
var sum = 0;
|
||||||
|
for (var j = 0; j < 1000; j++) {
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
sum += arr[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pushTime: pushTime,
|
||||||
|
accessTime: accessTime,
|
||||||
|
iterateTime: iterateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: Object Creation
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchObjectCreation() {
|
||||||
|
var literalTime = measureTime(function() {
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
var obj = { x: i, y: i * 2, z: i * 3 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function Point(x, y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
var constructorTime = measureTime(function() {
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
var p = new Point(i, i * 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var protoObj = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
move: function(dx, dy) {
|
||||||
|
this.x += dx;
|
||||||
|
this.y += dy;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var prototypeTime = measureTime(function() {
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
var obj = Object.create(protoObj);
|
||||||
|
obj.x = i;
|
||||||
|
obj.y = i * 2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
literalTime: literalTime,
|
||||||
|
constructorTime: constructorTime,
|
||||||
|
prototypeTime: prototypeTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: String Operations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchStringOps() {
|
||||||
|
var concatTime = measureTime(function() {
|
||||||
|
var str = "";
|
||||||
|
for (var i = 0; i < iterations.complex; i++) {
|
||||||
|
str = "test" + i + "value";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var strings = [];
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
strings.push("string" + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
var joinTime = measureTime(function() {
|
||||||
|
for (var i = 0; i < iterations.complex; i++) {
|
||||||
|
var result = strings.join(",");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var splitTime = measureTime(function() {
|
||||||
|
var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
var parts = str.split(",");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
concatTime: concatTime,
|
||||||
|
joinTime: joinTime,
|
||||||
|
splitTime: splitTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: Arithmetic Operations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchArithmetic() {
|
||||||
|
var intMathTime = measureTime(function() {
|
||||||
|
var result = 1;
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
result = ((result + i) * 2 - 1) / 3;
|
||||||
|
result = result % 1000 + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var floatMathTime = measureTime(function() {
|
||||||
|
var result = 1.5;
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
result = Math.sin(result) + Math.cos(i * 0.01);
|
||||||
|
result = Math.sqrt(Math.abs(result)) + 0.1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var bitwiseTime = measureTime(function() {
|
||||||
|
var result = 0;
|
||||||
|
for (var i = 0; i < iterations.simple; i++) {
|
||||||
|
result = (result ^ i) & 0xFFFF;
|
||||||
|
result = (result << 1) | (result >> 15);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
intMathTime: intMathTime,
|
||||||
|
floatMathTime: floatMathTime,
|
||||||
|
bitwiseTime: bitwiseTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Benchmark: Closure Operations
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function benchClosures() {
|
||||||
|
function makeAdder(x) {
|
||||||
|
return function(y) { return x + y; };
|
||||||
|
}
|
||||||
|
|
||||||
|
var closureCreateTime = measureTime(function() {
|
||||||
|
var funcs = [];
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
funcs.push(makeAdder(i));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var adders = [];
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
adders.push(makeAdder(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
var closureCallTime = measureTime(function() {
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < iterations.medium; i++) {
|
||||||
|
sum += adders[i % 1000](i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
closureCreateTime: closureCreateTime,
|
||||||
|
closureCallTime: closureCallTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Main benchmark runner
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
log.console("JavaScript Performance Benchmark");
|
||||||
|
log.console("======================\n");
|
||||||
|
|
||||||
|
// Property Access
|
||||||
|
log.console("BENCHMARK: Property Access");
|
||||||
|
var propResults = benchPropertyAccess();
|
||||||
|
log.console(" Read time: " + propResults.readTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / propResults.readTime).toFixed(1) + " reads/sec [" +
|
||||||
|
(propResults.readTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Write time: " + propResults.writeTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / propResults.writeTime).toFixed(1) + " writes/sec [" +
|
||||||
|
(propResults.writeTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
// Function Calls
|
||||||
|
log.console("BENCHMARK: Function Calls");
|
||||||
|
var funcResults = benchFunctionCalls();
|
||||||
|
log.console(" Simple calls: " + funcResults.simpleCallTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / funcResults.simpleCallTime).toFixed(1) + " calls/sec [" +
|
||||||
|
(funcResults.simpleCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Method calls: " + funcResults.methodCallTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / funcResults.methodCallTime).toFixed(1) + " calls/sec [" +
|
||||||
|
(funcResults.methodCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Complex calls: " + funcResults.complexCallTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / funcResults.complexCallTime).toFixed(1) + " calls/sec [" +
|
||||||
|
(funcResults.complexCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
// Array Operations
|
||||||
|
log.console("BENCHMARK: Array Operations");
|
||||||
|
var arrayResults = benchArrayOps();
|
||||||
|
log.console(" Push: " + arrayResults.pushTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / arrayResults.pushTime).toFixed(1) + " pushes/sec [" +
|
||||||
|
(arrayResults.pushTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Access: " + arrayResults.accessTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / arrayResults.accessTime).toFixed(1) + " accesses/sec [" +
|
||||||
|
(arrayResults.accessTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Iterate: " + arrayResults.iterateTime.toFixed(3) + "s => " +
|
||||||
|
(1000 / arrayResults.iterateTime).toFixed(1) + " full iterations/sec");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
// Object Creation
|
||||||
|
log.console("BENCHMARK: Object Creation");
|
||||||
|
var objResults = benchObjectCreation();
|
||||||
|
log.console(" Literal: " + objResults.literalTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / objResults.literalTime).toFixed(1) + " creates/sec [" +
|
||||||
|
(objResults.literalTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Constructor: " + objResults.constructorTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / objResults.constructorTime).toFixed(1) + " creates/sec [" +
|
||||||
|
(objResults.constructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Prototype: " + objResults.prototypeTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / objResults.prototypeTime).toFixed(1) + " creates/sec [" +
|
||||||
|
(objResults.prototypeTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
// String Operations
|
||||||
|
log.console("BENCHMARK: String Operations");
|
||||||
|
var strResults = benchStringOps();
|
||||||
|
log.console(" Concat: " + strResults.concatTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.complex / strResults.concatTime).toFixed(1) + " concats/sec [" +
|
||||||
|
(strResults.concatTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Join: " + strResults.joinTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.complex / strResults.joinTime).toFixed(1) + " joins/sec [" +
|
||||||
|
(strResults.joinTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Split: " + strResults.splitTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / strResults.splitTime).toFixed(1) + " splits/sec [" +
|
||||||
|
(strResults.splitTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
// Arithmetic Operations
|
||||||
|
log.console("BENCHMARK: Arithmetic Operations");
|
||||||
|
var mathResults = benchArithmetic();
|
||||||
|
log.console(" Integer math: " + mathResults.intMathTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / mathResults.intMathTime).toFixed(1) + " ops/sec [" +
|
||||||
|
(mathResults.intMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Float math: " + mathResults.floatMathTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / mathResults.floatMathTime).toFixed(1) + " ops/sec [" +
|
||||||
|
(mathResults.floatMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Bitwise: " + mathResults.bitwiseTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.simple / mathResults.bitwiseTime).toFixed(1) + " ops/sec [" +
|
||||||
|
(mathResults.bitwiseTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
// Closures
|
||||||
|
log.console("BENCHMARK: Closures");
|
||||||
|
var closureResults = benchClosures();
|
||||||
|
log.console(" Create: " + closureResults.closureCreateTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / closureResults.closureCreateTime).toFixed(1) + " creates/sec [" +
|
||||||
|
(closureResults.closureCreateTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console(" Call: " + closureResults.closureCallTime.toFixed(3) + "s => " +
|
||||||
|
(iterations.medium / closureResults.closureCallTime).toFixed(1) + " calls/sec [" +
|
||||||
|
(closureResults.closureCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
|
||||||
|
log.console("");
|
||||||
|
|
||||||
|
log.console("---------------------------------------------------------");
|
||||||
|
log.console("Benchmark complete.\n");
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
40
benchmarks/mandelbrot.ce
Normal file
40
benchmarks/mandelbrot.ce
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
var blob = use('blob')
|
||||||
|
|
||||||
|
var iter = 50, limit = 2.0;
|
||||||
|
var zr, zi, cr, ci, tr, ti;
|
||||||
|
|
||||||
|
var h = Number(arg[0]) || 500
|
||||||
|
var w = h
|
||||||
|
|
||||||
|
log.console(`P4\n${w} ${h}`);
|
||||||
|
|
||||||
|
for (let y = 0; y < h; ++y) {
|
||||||
|
// Create a blob for the row - we need w bits
|
||||||
|
var row = new blob(w);
|
||||||
|
|
||||||
|
for (let x = 0; x < w; ++x) {
|
||||||
|
zr = zi = tr = ti = 0;
|
||||||
|
cr = 2 * x / w - 1.5;
|
||||||
|
ci = 2 * y / h - 1;
|
||||||
|
for (let i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
|
||||||
|
zi = 2 * zr * zi + ci;
|
||||||
|
zr = tr - ti + cr;
|
||||||
|
tr = zr * zr;
|
||||||
|
ti = zi * zi;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a 1 bit if inside the set, 0 if outside
|
||||||
|
if (tr + ti <= limit * limit)
|
||||||
|
row.write_bit(1);
|
||||||
|
else
|
||||||
|
row.write_bit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the blob to stone (immutable) to prepare for output
|
||||||
|
stone(row)
|
||||||
|
|
||||||
|
// Output the blob data as raw bytes
|
||||||
|
log.console(text(row, 'b'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
11
benchmarks/montecarlo.ce
Normal file
11
benchmarks/montecarlo.ce
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
var N = 1000000;
|
||||||
|
var num = 0;
|
||||||
|
for (var i = 0; i < N; i ++) {
|
||||||
|
var x = 2 * $_.random();
|
||||||
|
var y = $_.random();
|
||||||
|
if (y < Math.sin(x * x))
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
log.console(2 * num / N);
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
161
benchmarks/nbody.ce
Normal file
161
benchmarks/nbody.ce
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
var PI = Math.PI;
|
||||||
|
var SOLAR_MASS = 4 * PI * PI;
|
||||||
|
var DAYS_PER_YEAR = 365.24;
|
||||||
|
|
||||||
|
function Body(x, y, z, vx, vy, vz, mass) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
this.vx = vx;
|
||||||
|
this.vy = vy;
|
||||||
|
this.vz = vz;
|
||||||
|
this.mass = mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Jupiter() {
|
||||||
|
return new Body(
|
||||||
|
4.84143144246472090e+00,
|
||||||
|
-1.16032004402742839e+00,
|
||||||
|
-1.03622044471123109e-01,
|
||||||
|
1.66007664274403694e-03 * DAYS_PER_YEAR,
|
||||||
|
7.69901118419740425e-03 * DAYS_PER_YEAR,
|
||||||
|
-6.90460016972063023e-05 * DAYS_PER_YEAR,
|
||||||
|
9.54791938424326609e-04 * SOLAR_MASS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Saturn() {
|
||||||
|
return new Body(
|
||||||
|
8.34336671824457987e+00,
|
||||||
|
4.12479856412430479e+00,
|
||||||
|
-4.03523417114321381e-01,
|
||||||
|
-2.76742510726862411e-03 * DAYS_PER_YEAR,
|
||||||
|
4.99852801234917238e-03 * DAYS_PER_YEAR,
|
||||||
|
2.30417297573763929e-05 * DAYS_PER_YEAR,
|
||||||
|
2.85885980666130812e-04 * SOLAR_MASS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Uranus() {
|
||||||
|
return new Body(
|
||||||
|
1.28943695621391310e+01,
|
||||||
|
-1.51111514016986312e+01,
|
||||||
|
-2.23307578892655734e-01,
|
||||||
|
2.96460137564761618e-03 * DAYS_PER_YEAR,
|
||||||
|
2.37847173959480950e-03 * DAYS_PER_YEAR,
|
||||||
|
-2.96589568540237556e-05 * DAYS_PER_YEAR,
|
||||||
|
4.36624404335156298e-05 * SOLAR_MASS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Neptune() {
|
||||||
|
return new Body(
|
||||||
|
1.53796971148509165e+01,
|
||||||
|
-2.59193146099879641e+01,
|
||||||
|
1.79258772950371181e-01,
|
||||||
|
2.68067772490389322e-03 * DAYS_PER_YEAR,
|
||||||
|
1.62824170038242295e-03 * DAYS_PER_YEAR,
|
||||||
|
-9.51592254519715870e-05 * DAYS_PER_YEAR,
|
||||||
|
5.15138902046611451e-05 * SOLAR_MASS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Sun() {
|
||||||
|
return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodies = Array(Sun(), Jupiter(), Saturn(), Uranus(), Neptune());
|
||||||
|
|
||||||
|
function offsetMomentum() {
|
||||||
|
let px = 0;
|
||||||
|
let py = 0;
|
||||||
|
let pz = 0;
|
||||||
|
var size = bodies.length;
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
var body = bodies[i];
|
||||||
|
var mass = body.mass;
|
||||||
|
px += body.vx * mass;
|
||||||
|
py += body.vy * mass;
|
||||||
|
pz += body.vz * mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = bodies[0];
|
||||||
|
body.vx = -px / SOLAR_MASS;
|
||||||
|
body.vy = -py / SOLAR_MASS;
|
||||||
|
body.vz = -pz / SOLAR_MASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function advance(dt) {
|
||||||
|
var size = bodies.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
var bodyi = bodies[i];
|
||||||
|
let vxi = bodyi.vx;
|
||||||
|
let vyi = bodyi.vy;
|
||||||
|
let vzi = bodyi.vz;
|
||||||
|
for (let j = i + 1; j < size; j++) {
|
||||||
|
var bodyj = bodies[j];
|
||||||
|
var dx = bodyi.x - bodyj.x;
|
||||||
|
var dy = bodyi.y - bodyj.y;
|
||||||
|
var dz = bodyi.z - bodyj.z;
|
||||||
|
|
||||||
|
var d2 = dx * dx + dy * dy + dz * dz;
|
||||||
|
var mag = dt / (d2 * Math.sqrt(d2));
|
||||||
|
|
||||||
|
var massj = bodyj.mass;
|
||||||
|
vxi -= dx * massj * mag;
|
||||||
|
vyi -= dy * massj * mag;
|
||||||
|
vzi -= dz * massj * mag;
|
||||||
|
|
||||||
|
var massi = bodyi.mass;
|
||||||
|
bodyj.vx += dx * massi * mag;
|
||||||
|
bodyj.vy += dy * massi * mag;
|
||||||
|
bodyj.vz += dz * massi * mag;
|
||||||
|
}
|
||||||
|
bodyi.vx = vxi;
|
||||||
|
bodyi.vy = vyi;
|
||||||
|
bodyi.vz = vzi;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
var body = bodies[i];
|
||||||
|
body.x += dt * body.vx;
|
||||||
|
body.y += dt * body.vy;
|
||||||
|
body.z += dt * body.vz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function energy() {
|
||||||
|
let e = 0;
|
||||||
|
var size = bodies.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
var bodyi = bodies[i];
|
||||||
|
|
||||||
|
e += 0.5 * bodyi.mass * ( bodyi.vx * bodyi.vx +
|
||||||
|
bodyi.vy * bodyi.vy + bodyi.vz * bodyi.vz );
|
||||||
|
|
||||||
|
for (let j = i + 1; j < size; j++) {
|
||||||
|
var bodyj = bodies[j];
|
||||||
|
var dx = bodyi.x - bodyj.x;
|
||||||
|
var dy = bodyi.y - bodyj.y;
|
||||||
|
var dz = bodyi.z - bodyj.z;
|
||||||
|
|
||||||
|
var distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
e -= (bodyi.mass * bodyj.mass) / distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var n = arg[0] || 1000000
|
||||||
|
|
||||||
|
offsetMomentum();
|
||||||
|
|
||||||
|
log.console(`n = ${n}`)
|
||||||
|
log.console(energy().toFixed(9))
|
||||||
|
for (let i = 0; i < n; i++)
|
||||||
|
advance(0.01);
|
||||||
|
log.console(energy().toFixed(9))
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
@@ -42,34 +42,34 @@ for (let i = 0; i < 100; i++) {
|
|||||||
|
|
||||||
// Calculate statistics
|
// Calculate statistics
|
||||||
function getStats(arr) {
|
function getStats(arr) {
|
||||||
const avg = arr.reduce((a, b) => a + b) / arr.length;
|
def avg = arr.reduce((a, b) => a + b) / arr.length;
|
||||||
const min = Math.min(...arr);
|
def min = Math.min(...arr);
|
||||||
const max = Math.max(...arr);
|
def max = Math.max(...arr);
|
||||||
return { avg, min, max };
|
return { avg, min, max };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretty print results
|
// Pretty print results
|
||||||
log.console("\n=== Performance Test Results (100 iterations) ===");
|
log.console("\n== Performance Test Results (100 iterations) ==");
|
||||||
log.console("\nJSON Decoding (ms):");
|
log.console("\nJSON Decoding (ms):");
|
||||||
const jsonDecStats = getStats(jsonDecodeTimes);
|
def jsonDecStats = getStats(jsonDecodeTimes);
|
||||||
log.console(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
|
log.console(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
|
||||||
log.console(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
|
log.console(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
|
||||||
log.console(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
|
log.console(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
|
||||||
|
|
||||||
log.console("\nJSON Encoding (ms):");
|
log.console("\nJSON Encoding (ms):");
|
||||||
const jsonEncStats = getStats(jsonEncodeTimes);
|
def jsonEncStats = getStats(jsonEncodeTimes);
|
||||||
log.console(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
|
log.console(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
|
||||||
log.console(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
|
log.console(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
|
||||||
log.console(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
|
log.console(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
|
||||||
|
|
||||||
log.console("\nNOTA Encoding (ms):");
|
log.console("\nNOTA Encoding (ms):");
|
||||||
const notaEncStats = getStats(notaEncodeTimes);
|
def notaEncStats = getStats(notaEncodeTimes);
|
||||||
log.console(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
|
log.console(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
|
||||||
log.console(`Min: ${notaEncStats.min.toFixed(2)} ms`);
|
log.console(`Min: ${notaEncStats.min.toFixed(2)} ms`);
|
||||||
log.console(`Max: ${notaEncStats.max.toFixed(2)} ms`);
|
log.console(`Max: ${notaEncStats.max.toFixed(2)} ms`);
|
||||||
|
|
||||||
log.console("\nNOTA Decoding (ms):");
|
log.console("\nNOTA Decoding (ms):");
|
||||||
const notaDecStats = getStats(notaDecodeTimes);
|
def notaDecStats = getStats(notaDecodeTimes);
|
||||||
log.console(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
|
log.console(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
|
||||||
log.console(`Min: ${notaDecStats.min.toFixed(2)} ms`);
|
log.console(`Min: ${notaDecStats.min.toFixed(2)} ms`);
|
||||||
log.console(`Max: ${notaDecStats.max.toFixed(2)} ms`);
|
log.console(`Max: ${notaDecStats.max.toFixed(2)} ms`);
|
||||||
|
|||||||
50
benchmarks/spectral-norm.ce
Normal file
50
benchmarks/spectral-norm.ce
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
function A(i,j) {
|
||||||
|
return 1/((i+j)*(i+j+1)/2+i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Au(u,v) {
|
||||||
|
for (var i=0; i<u.length; ++i) {
|
||||||
|
var t = 0;
|
||||||
|
for (var j=0; j<u.length; ++j)
|
||||||
|
t += A(i,j) * u[j];
|
||||||
|
|
||||||
|
v[i] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Atu(u,v) {
|
||||||
|
for (var i=0; i<u.length; ++i) {
|
||||||
|
var t = 0;
|
||||||
|
for (var j=0; j<u.length; ++j)
|
||||||
|
t += A(j,i) * u[j];
|
||||||
|
|
||||||
|
v[i] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function AtAu(u,v,w) {
|
||||||
|
Au(u,w);
|
||||||
|
Atu(w,v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function spectralnorm(n) {
|
||||||
|
var i, u=[], v=[], w=[], vv=0, vBv=0;
|
||||||
|
for (i=0; i<n; ++i)
|
||||||
|
u[i] = 1; v[i] = w[i] = 0;
|
||||||
|
|
||||||
|
for (i=0; i<10; ++i) {
|
||||||
|
AtAu(u,v,w);
|
||||||
|
AtAu(v,u,w);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<n; ++i) {
|
||||||
|
vBv += u[i]*v[i];
|
||||||
|
vv += v[i]*v[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.sqrt(vBv/vv);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.console(spectralnorm(arg[0]).toFixed(9));
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
@@ -36,7 +36,7 @@ function roundTripWota(value) {
|
|||||||
// iterations: how many times to loop
|
// iterations: how many times to loop
|
||||||
//
|
//
|
||||||
// You can tweak these as you like for heavier or lighter tests.
|
// You can tweak these as you like for heavier or lighter tests.
|
||||||
const benchmarks = [
|
def benchmarks = [
|
||||||
{
|
{
|
||||||
name: "Small Integers",
|
name: "Small Integers",
|
||||||
data: [0, 42, -1, 2023],
|
data: [0, 42, -1, 2023],
|
||||||
@@ -76,7 +76,7 @@ const benchmarks = [
|
|||||||
|
|
||||||
// Print a header
|
// Print a header
|
||||||
log.console("Wota Encode/Decode Benchmark");
|
log.console("Wota Encode/Decode Benchmark");
|
||||||
log.console("============================\n");
|
log.console("===================\n");
|
||||||
|
|
||||||
// We'll run each benchmark scenario in turn.
|
// We'll run each benchmark scenario in turn.
|
||||||
for (let bench of benchmarks) {
|
for (let bench of benchmarks) {
|
||||||
|
|||||||
@@ -2,43 +2,53 @@
|
|||||||
// benchmark_wota_nota_json.js
|
// benchmark_wota_nota_json.js
|
||||||
//
|
//
|
||||||
// Usage in QuickJS:
|
// Usage in QuickJS:
|
||||||
// qjs benchmark_wota_nota_json.js
|
// qjs benchmark_wota_nota_json.js <LibraryName> <ScenarioName>
|
||||||
//
|
//
|
||||||
// Ensure wota, nota, json, and os are all available, e.g.:
|
// Ensure wota, nota, json, and os are all available, e.g.:
|
||||||
var wota = use('wota');
|
var wota = use('wota');
|
||||||
var nota = use('nota');
|
var nota = use('nota');
|
||||||
var json = use('json');
|
var json = use('json');
|
||||||
|
var jswota = use('jswota')
|
||||||
var os = use('os');
|
var os = use('os');
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
if (arg.length != 2) {
|
||||||
|
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');
|
||||||
|
$_.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
var lib_name = arg[0];
|
||||||
|
var scenario_name = arg[1];
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// 1. Setup "libraries" array to easily switch among Wota, Nota, and JSON
|
// 1. Setup "libraries" array to easily switch among wota, nota, and json
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const libraries = [
|
def libraries = [
|
||||||
{
|
{
|
||||||
name: "Wota",
|
name: "wota",
|
||||||
encode: wota.encode,
|
encode: wota.encode,
|
||||||
decode: wota.decode,
|
decode: wota.decode,
|
||||||
// Wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
|
// wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
|
||||||
getSize(encoded) {
|
getSize(encoded) {
|
||||||
return encoded.byteLength;
|
return encoded.length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Nota",
|
name: "nota",
|
||||||
encode: nota.encode,
|
encode: nota.encode,
|
||||||
decode: nota.decode,
|
decode: nota.decode,
|
||||||
// Nota also produces an ArrayBuffer:
|
// nota also produces an ArrayBuffer:
|
||||||
getSize(encoded) {
|
getSize(encoded) {
|
||||||
return encoded.byteLength;
|
return encoded.length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "JSON",
|
name: "json",
|
||||||
encode: json.encode,
|
encode: json.encode,
|
||||||
decode: json.decode,
|
decode: json.decode,
|
||||||
// JSON produces a JS string. We'll measure its UTF-16 code unit length
|
// json produces a JS string. We'll measure its UTF-16 code unit length
|
||||||
// as a rough "size". Alternatively, you could convert to UTF-8 for
|
// as a rough "size". Alternatively, you could convert to UTF-8 for
|
||||||
// a more accurate byte size. Here we just use `string.length`.
|
// a more accurate byte size. Here we just use `string.length`.
|
||||||
getSize(encodedStr) {
|
getSize(encodedStr) {
|
||||||
@@ -52,29 +62,29 @@ const libraries = [
|
|||||||
// Each scenario has { name, data, iterations }
|
// Each scenario has { name, data, iterations }
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const benchmarks = [
|
def benchmarks = [
|
||||||
{
|
{
|
||||||
name: "Empty object",
|
name: "empty",
|
||||||
data: [{}, {}, {}, {}],
|
data: [{}, {}, {}, {}],
|
||||||
iterations: 10000
|
iterations: 10000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Small Integers",
|
name: "integers",
|
||||||
data: [0, 42, -1, 2023],
|
data: [0, 42, -1, 2023],
|
||||||
iterations: 100000
|
iterations: 100000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Floating point",
|
name: "floats",
|
||||||
data: [0.1, 1e-50, 3.14159265359],
|
data: [0.1, 1e-50, 3.14159265359],
|
||||||
iterations: 100000
|
iterations: 100000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Strings (short, emoji)",
|
name: "strings",
|
||||||
data: ["Hello, Wota!", "short", "Emoji: \u{1f600}\u{1f64f}"],
|
data: ["Hello, wota!", "short", "Emoji: \u{1f600}\u{1f64f}"],
|
||||||
iterations: 100000
|
iterations: 100000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Small Objects",
|
name: "objects",
|
||||||
data: [
|
data: [
|
||||||
{ a:1, b:2.2, c:"3", d:false },
|
{ a:1, b:2.2, c:"3", d:false },
|
||||||
{ x:42, y:null, z:"test" }
|
{ x:42, y:null, z:"test" }
|
||||||
@@ -82,20 +92,15 @@ const benchmarks = [
|
|||||||
iterations: 50000
|
iterations: 50000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Nested Arrays",
|
name: "nested",
|
||||||
data: [ [ [ [1,2], [3,4] ] ], [[[]]], [1, [2, [3, [4]]]] ],
|
data: [ [ [ [1,2], [3,4] ] ], [[[]]], [1, [2, [3, [4]]]] ],
|
||||||
iterations: 50000
|
iterations: 50000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Large Array (1k integers)",
|
name: "large_array",
|
||||||
data: [ Array.from({length:1000}, (_, i) => i) ],
|
data: [ Array.from({length:1000}, (_, i) => i) ],
|
||||||
iterations: 1000
|
iterations: 1000
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Large Binary Blob (256KB)",
|
|
||||||
data: [ new Uint8Array(256 * 1024).buffer ],
|
|
||||||
iterations: 200
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -113,7 +118,7 @@ function measureTime(fn) {
|
|||||||
// 4. For each library, we run each benchmark scenario and measure:
|
// 4. For each library, we run each benchmark scenario and measure:
|
||||||
// - Encoding time (seconds)
|
// - Encoding time (seconds)
|
||||||
// - Decoding time (seconds)
|
// - Decoding time (seconds)
|
||||||
// - Total encoded size (bytes or code units for JSON)
|
// - Total encoded size (bytes or code units for json)
|
||||||
//
|
//
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -134,7 +139,7 @@ function runBenchmarkForLibrary(lib, bench) {
|
|||||||
let e = lib.encode(bench.data[j]);
|
let e = lib.encode(bench.data[j]);
|
||||||
// store only in the very first iteration, so we can decode them later
|
// store only in the very first iteration, so we can decode them later
|
||||||
// but do not store them every iteration or we blow up memory.
|
// but do not store them every iteration or we blow up memory.
|
||||||
if (i === 0) {
|
if (i == 0) {
|
||||||
encodedList.push(e);
|
encodedList.push(e);
|
||||||
totalSize += lib.getSize(e);
|
totalSize += lib.getSize(e);
|
||||||
}
|
}
|
||||||
@@ -157,33 +162,43 @@ function runBenchmarkForLibrary(lib, bench) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// 5. Main driver: run across all benchmarks, for each library.
|
// 5. Main driver: run only the specified library and scenario
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
log.console("Benchmark: Wota vs Nota vs JSON");
|
// Find the requested library and scenario
|
||||||
log.console("================================\n");
|
var lib = libraries.find(l => l.name == lib_name);
|
||||||
|
var bench = benchmarks.find(b => b.name == scenario_name);
|
||||||
|
|
||||||
for (let bench of benchmarks) {
|
if (!lib) {
|
||||||
log.console(`SCENARIO: ${bench.name}`);
|
log.console('Unknown library:', lib_name);
|
||||||
log.console(` Data length: ${bench.data.length} | Iterations: ${bench.iterations}\n`);
|
log.console('Available libraries:', libraries.map(l => l.name).join(', '));
|
||||||
|
$_.stop()
|
||||||
for (let lib of libraries) {
|
|
||||||
let { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
|
|
||||||
|
|
||||||
// We'll compute total operations = bench.iterations * bench.data.length
|
|
||||||
let totalOps = bench.iterations * bench.data.length;
|
|
||||||
let encOpsPerSec = (totalOps / encodeTime).toFixed(1);
|
|
||||||
let decOpsPerSec = (totalOps / decodeTime).toFixed(1);
|
|
||||||
|
|
||||||
log.console(` ${lib.name}:`);
|
|
||||||
log.console(` Encode time: ${encodeTime.toFixed(3)}s => ${encOpsPerSec} encodes/sec [${(encodeTime/bench.iterations)*1000000000} ns/try]`);
|
|
||||||
log.console(` Decode time: ${decodeTime.toFixed(3)}s => ${decOpsPerSec} decodes/sec [${(decodeTime/bench.iterations)*1000000000}/try]`);
|
|
||||||
log.console(` Total size: ${totalSize} bytes (or code units for JSON)`);
|
|
||||||
log.console("");
|
|
||||||
}
|
|
||||||
log.console("---------------------------------------------------------\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.console("Benchmark complete.\n");
|
if (!bench) {
|
||||||
|
log.console('Unknown scenario:', scenario_name);
|
||||||
|
log.console('Available scenarios:', benchmarks.map(b => b.name).join(', '));
|
||||||
|
$_.stop()
|
||||||
|
}
|
||||||
|
|
||||||
os.exit()
|
// Run the benchmark for this library/scenario combination
|
||||||
|
var { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
|
||||||
|
|
||||||
|
// Output json for easy parsing by hyperfine or other tools
|
||||||
|
var totalOps = bench.iterations * bench.data.length;
|
||||||
|
var result = {
|
||||||
|
lib: lib_name,
|
||||||
|
scenario: scenario_name,
|
||||||
|
encodeTime: encodeTime,
|
||||||
|
decodeTime: decodeTime,
|
||||||
|
totalSize: totalSize,
|
||||||
|
totalOps: totalOps,
|
||||||
|
encodeOpsPerSec: totalOps / encodeTime,
|
||||||
|
decodeOpsPerSec: totalOps / decodeTime,
|
||||||
|
encodeNsPerOp: (encodeTime / totalOps) * 1e9,
|
||||||
|
decodeNsPerOp: (decodeTime / totalOps) * 1e9
|
||||||
|
};
|
||||||
|
|
||||||
|
log.console(json.encode(result));
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
|
|||||||
124
cell.md
Normal file
124
cell.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
JAVASCRIPT VISION
|
||||||
|
|
||||||
|
I see objects as being a sort of combination of a lisp cell and a record: symbols, which are used internally, and are private and non iterable, and record string values, which are iterable, readable, and writable; of course everything becomes locked in when stone.
|
||||||
|
|
||||||
|
CELLSCRIPT
|
||||||
|
|
||||||
|
Javascript to its core. Objects. What does the language need? It can be quite small, I think. The key is, ANYTHING that we want to be fast and JIT'd, must be present. So, record lookups. These are actually quicker in a jit'd language that have them as a feature. Most things should be libraries. Blobs need to be in the runtime.
|
||||||
|
|
||||||
|
The purpose of this is to be a great language for passing messages. So it should be fast at creating records first and foremost, and finding items on them. So it needs first class, jitt'd records.
|
||||||
|
|
||||||
|
Finally, it needs to use less memory. Deleting a bunch of this stuff should make that simpler.
|
||||||
|
|
||||||
|
What is present?
|
||||||
|
Objects, prototypes, numbers, arrays, strings, true, false, null.
|
||||||
|
|
||||||
|
Things to do:
|
||||||
|
|
||||||
|
merge typeof and instanceof. Misty has array? stone? number? etc; it needs to be generic. 5 is number returns true.
|
||||||
|
|
||||||
|
No new operator. It's the same idea though: simply instead of 'var guy = new sprite({x,y})' you would say 'var guy = sprite({x,y})', and sprite would simply be a function written to return a sprite object.
|
||||||
|
|
||||||
|
One number type. Dec64. Numeric stack can be added in later: a bigint library, for example, built inside cell.
|
||||||
|
|
||||||
|
Simplify the property attributes stuff. It is simple: objects have text keys and whatever values. Objects can also have objects as values. These work like symbols. You can share them, if desired. No well known symbols exist to eliminate that much misdirection. Obejcts basically work like private keys. If you serialize an object, objects that are keys are not serialized; only textual keys are. You can do something about it with a json() method that is invoked, if you desire. You cannot retrieve
|
||||||
|
|
||||||
|
var works like let; use var instead of let
|
||||||
|
|
||||||
|
no const
|
||||||
|
|
||||||
|
Function closures and _ => all work the same and close over the 'this' variable
|
||||||
|
|
||||||
|
Totally delete modules, coroutines, generators, proxy .. this deletes a lot of the big switch statement
|
||||||
|
|
||||||
|
Add the 'go' statement for tail calls
|
||||||
|
|
||||||
|
Add the 'do' statement
|
||||||
|
|
||||||
|
Implementation detail: separate out arrays and objects. They are not the same. Objects no longer need to track if they're fast arrays or not. They're not. Arrays are. Always.
|
||||||
|
|
||||||
|
Add the functional proxy idea. Log will be implemented through that.
|
||||||
|
|
||||||
|
Remove ===; it's just == now, and !=.
|
||||||
|
|
||||||
|
Remove 'continue'; now, break handles both. For a do statement, label it, and break to that label; so
|
||||||
|
|
||||||
|
var x = 0
|
||||||
|
do loop {
|
||||||
|
x++
|
||||||
|
if (x < 5) break loop // goes back to loop
|
||||||
|
break // exits loop
|
||||||
|
}
|
||||||
|
|
||||||
|
rename instanceof to 'is'
|
||||||
|
|
||||||
|
remove undefined; all are 'null' now
|
||||||
|
|
||||||
|
remove 'delete'; to remove a field, assign it to null
|
||||||
|
|
||||||
|
remove with
|
||||||
|
|
||||||
|
Remove Object. New records have a prototype of nothing. There are no more 'type prototypes' at all.
|
||||||
|
|
||||||
|
Arrays are their own type
|
||||||
|
|
||||||
|
Remove property descriptors. Properties are always settable, unless the object as a whole is stone. Stone is an object property instead of a shape property.
|
||||||
|
|
||||||
|
Syntax stuff .. would like to invoke functions without (). This can effectively simulate a "getter". Make ? and all other characters usable for names. No reserve words, which are endlessly irritating.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
This will all actually come about gradually. Add a few things at a time, fix up code that did not adhere. For a lot of this, no new functions will even need to be written; it's a matter of not calling certain functions that are no longer relevant, or calling different functions when required.
|
||||||
|
|
||||||
|
|
||||||
|
## Benchmarks to implement
|
||||||
|
### general speed
|
||||||
|
binarytrees
|
||||||
|
coro-prime-sieve
|
||||||
|
edigits
|
||||||
|
fannkuch-redux
|
||||||
|
fasta
|
||||||
|
http-server
|
||||||
|
json serialize/deserialize
|
||||||
|
knucleotide
|
||||||
|
lru
|
||||||
|
mandelbrot
|
||||||
|
merkletrees
|
||||||
|
nbody
|
||||||
|
nsieve
|
||||||
|
pidigits
|
||||||
|
regex-redux
|
||||||
|
secp256k1
|
||||||
|
spectral-norm
|
||||||
|
|
||||||
|
### function calling and recursion stress - test goto
|
||||||
|
naive recursive fibonacci [fib(35) or fib(40)]
|
||||||
|
tak
|
||||||
|
ackermann
|
||||||
|
|
||||||
|
### numeric
|
||||||
|
sieve of eratosthenes [10^7 bits]
|
||||||
|
spectral norm [5500 x 5500 matrix]
|
||||||
|
n-body sim [50 000 - 100 000 steps]
|
||||||
|
mandelbrot [1600x1200 image, max iter = 50]
|
||||||
|
|
||||||
|
### memory & gc torture
|
||||||
|
binary trees [depth 18 (~500 000 nodes)]
|
||||||
|
richards task scheduler
|
||||||
|
fannkuch redux [n=11 or 12]
|
||||||
|
|
||||||
|
### dynamic object & property access
|
||||||
|
deltablue constraint solver
|
||||||
|
splay tree [256k nodes]
|
||||||
|
json, wota, nota decode->encode [use 2MB example]
|
||||||
|
|
||||||
|
### string / regex kernels
|
||||||
|
regex-DNA
|
||||||
|
fasta
|
||||||
|
word-frequency
|
||||||
|
|
||||||
|
### concurrency/message passing
|
||||||
|
ping-pong [two actors exhange a small record N times, 1M messages end to end]
|
||||||
|
chameneos [mating color swap game w/ randezvous]
|
||||||
|
|
||||||
|
For all, track memory and time.
|
||||||
@@ -18,7 +18,7 @@ var state = {
|
|||||||
|
|
||||||
// Helper to calculate progress percentage
|
// Helper to calculate progress percentage
|
||||||
function get_progress() {
|
function get_progress() {
|
||||||
if (state.total_bytes === 0) {
|
if (state.total_bytes == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return Math.round((state.downloaded_bytes / state.total_bytes) * 100);
|
return Math.round((state.downloaded_bytes / state.total_bytes) * 100);
|
||||||
@@ -150,7 +150,7 @@ function read_next_chunk() {
|
|||||||
try {
|
try {
|
||||||
var chunk = http.fetch_read_chunk(state.connection);
|
var chunk = http.fetch_read_chunk(state.connection);
|
||||||
|
|
||||||
if (chunk === null) {
|
if (chunk == null) {
|
||||||
// Download complete
|
// Download complete
|
||||||
finish_download();
|
finish_download();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ $_.portal(e => {
|
|||||||
send(waiting_client, e.actor)
|
send(waiting_client, e.actor)
|
||||||
send(e, waiting_client.actor)
|
send(e, waiting_client.actor)
|
||||||
|
|
||||||
waiting_client = undefined
|
waiting_client = null
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
47
meson.build
47
meson.build
@@ -115,11 +115,20 @@ endif
|
|||||||
if host_machine.system() == 'linux'
|
if host_machine.system() == 'linux'
|
||||||
deps += cc.find_library('asound', required:true)
|
deps += cc.find_library('asound', required:true)
|
||||||
deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')]
|
deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')]
|
||||||
|
deps += cc.find_library('blas', required:true)
|
||||||
|
deps += cc.find_library('lapack', required:true)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
deps += cc.find_library('d3d11')
|
deps += cc.find_library('d3d11')
|
||||||
deps += cc.find_library('ws2_32', required:true)
|
deps += cc.find_library('ws2_32', required:true)
|
||||||
|
# For Windows, you may need to install OpenBLAS or Intel MKL
|
||||||
|
# and adjust these library names accordingly
|
||||||
|
deps += cc.find_library('openblas', required:false)
|
||||||
|
if not cc.find_library('openblas', required:false).found()
|
||||||
|
deps += cc.find_library('blas', required:false)
|
||||||
|
deps += cc.find_library('lapack', required:false)
|
||||||
|
endif
|
||||||
deps += cc.find_library('dbghelp')
|
deps += cc.find_library('dbghelp')
|
||||||
deps += cc.find_library('winmm')
|
deps += cc.find_library('winmm')
|
||||||
deps += cc.find_library('setupapi')
|
deps += cc.find_library('setupapi')
|
||||||
@@ -152,32 +161,6 @@ else
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
quickjs_opts = []
|
|
||||||
quickjs_opts += 'default_library=static'
|
|
||||||
|
|
||||||
# Enable leak detection for non-release builds
|
|
||||||
if get_option('buildtype') != 'release'
|
|
||||||
quickjs_opts += 'leaks=true'
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Try to find system-installed quickjs first
|
|
||||||
quickjs_dep = dependency('quickjs', static: true, required: false)
|
|
||||||
if not quickjs_dep.found()
|
|
||||||
message('⚙ System quickjs not found, building subproject...')
|
|
||||||
deps += dependency('quickjs', static:true, default_options:quickjs_opts)
|
|
||||||
else
|
|
||||||
deps += quickjs_dep
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Try to find system-installed qjs-layout first
|
|
||||||
qjs_layout_dep = dependency('qjs-layout', static: true, required: false)
|
|
||||||
if not qjs_layout_dep.found()
|
|
||||||
message('⚙ System qjs-layout not found, building subproject...')
|
|
||||||
deps += dependency('qjs-layout', static:true)
|
|
||||||
else
|
|
||||||
deps += qjs_layout_dep
|
|
||||||
endif
|
|
||||||
|
|
||||||
miniz_dep = dependency('miniz', static: true, required: false)
|
miniz_dep = dependency('miniz', static: true, required: false)
|
||||||
if not miniz_dep.found()
|
if not miniz_dep.found()
|
||||||
message('⚙ System miniz not found, building subproject...')
|
message('⚙ System miniz not found, building subproject...')
|
||||||
@@ -196,6 +179,7 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
deps += dependency('threads')
|
deps += dependency('threads')
|
||||||
|
deps += dependency('mimalloc')
|
||||||
|
|
||||||
# Try to find system-installed chipmunk first
|
# Try to find system-installed chipmunk first
|
||||||
chipmunk_dep = dependency('chipmunk', static: true, required: false)
|
chipmunk_dep = dependency('chipmunk', static: true, required: false)
|
||||||
@@ -206,6 +190,8 @@ else
|
|||||||
deps += chipmunk_dep
|
deps += chipmunk_dep
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
deps += dependency('libffi', static:true)
|
||||||
|
|
||||||
if host_machine.system() != 'emscripten'
|
if host_machine.system() != 'emscripten'
|
||||||
# Try to find system-installed enet first
|
# Try to find system-installed enet first
|
||||||
enet_dep = dependency('enet', static: true, required: false)
|
enet_dep = dependency('enet', static: true, required: false)
|
||||||
@@ -217,7 +203,7 @@ if host_machine.system() != 'emscripten'
|
|||||||
endif
|
endif
|
||||||
src += 'qjs_enet.c'
|
src += 'qjs_enet.c'
|
||||||
|
|
||||||
tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true']
|
tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true', 'on_demand=true']
|
||||||
add_project_arguments('-DTRACY_ENABLE', language:['c','cpp'])
|
add_project_arguments('-DTRACY_ENABLE', language:['c','cpp'])
|
||||||
|
|
||||||
# Try to find system-installed tracy first
|
# Try to find system-installed tracy first
|
||||||
@@ -286,8 +272,8 @@ sources = []
|
|||||||
src += [
|
src += [
|
||||||
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
|
'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',
|
'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_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',
|
'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'
|
'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', 'qjs_num.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'point.c', 'qjs_ffi.c'
|
||||||
]
|
]
|
||||||
# quirc src
|
# quirc src
|
||||||
src += [
|
src += [
|
||||||
@@ -295,6 +281,9 @@ src += [
|
|||||||
'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c'
|
'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# quickjs src
|
||||||
|
src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
|
||||||
|
|
||||||
imsrc = [
|
imsrc = [
|
||||||
'GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp',
|
'GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp',
|
||||||
'imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp',
|
'imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var graphics = use('graphics')
|
|||||||
var color = use('color')
|
var color = use('color')
|
||||||
|
|
||||||
var sprite = {
|
var sprite = {
|
||||||
image: undefined,
|
image: null,
|
||||||
set color(x) { this._sprite.color = x; },
|
set color(x) { this._sprite.color = x; },
|
||||||
get color() { return this._sprite.color; },
|
get color() { return this._sprite.color; },
|
||||||
anim_speed: 1,
|
anim_speed: 1,
|
||||||
@@ -12,7 +12,7 @@ var sprite = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof str === 'string') {
|
if (typeof str == 'string') {
|
||||||
if (!this.animset[str]) {
|
if (!this.animset[str]) {
|
||||||
fn?.();
|
fn?.();
|
||||||
return;
|
return;
|
||||||
@@ -26,8 +26,8 @@ var sprite = {
|
|||||||
|
|
||||||
this.del_anim?.();
|
this.del_anim?.();
|
||||||
this.del_anim = () => {
|
this.del_anim = () => {
|
||||||
this.del_anim = undefined;
|
this.del_anim = null;
|
||||||
advance = undefined;
|
advance = null;
|
||||||
stop?.();
|
stop?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,10 +38,10 @@ var sprite = {
|
|||||||
var done = false;
|
var done = false;
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
f = (((f - 1) % playing.frames.length) + playing.frames.length) % playing.frames.length;
|
f = (((f - 1) % playing.frames.length) + playing.frames.length) % playing.frames.length;
|
||||||
if (f === playing.frames.length - 1) done = true;
|
if (f == playing.frames.length - 1) done = true;
|
||||||
} else {
|
} else {
|
||||||
f = (f + 1) % playing.frames.length;
|
f = (f + 1) % playing.frames.length;
|
||||||
if (f === 0) done = true;
|
if (f == 0) done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.image = playing.frames[f];
|
this.image = playing.frames[f];
|
||||||
@@ -102,11 +102,11 @@ var sprite = {
|
|||||||
},
|
},
|
||||||
garbage: function() {
|
garbage: function() {
|
||||||
this.del_anim?.();
|
this.del_anim?.();
|
||||||
this.anim = undefined;
|
this.anim = null;
|
||||||
tree.delete(this._sprite)
|
tree.delete(this._sprite)
|
||||||
this.transform.parent = undefined
|
this.transform.parent = null
|
||||||
for (var t of this.transform.children())
|
for (var t of this.transform.children())
|
||||||
t.parent = undefined
|
t.parent = null
|
||||||
delete this.transform.sprite
|
delete this.transform.sprite
|
||||||
delete this._sprite
|
delete this._sprite
|
||||||
// log.console("CLEARED SPRITE")
|
// log.console("CLEARED SPRITE")
|
||||||
@@ -216,7 +216,7 @@ sprite.to_queue = function(ysort = false)
|
|||||||
};
|
};
|
||||||
var culled = sprite.tree.query(camrect)
|
var culled = sprite.tree.query(camrect)
|
||||||
if (culled.length == 0) return [];
|
if (culled.length == 0) return [];
|
||||||
var cmd = graphics.make_sprite_queue(culled, prosperon.camera, undefined, 1);
|
var cmd = graphics.make_sprite_queue(culled, prosperon.camera, null, 1);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,18 +11,18 @@ var input = use('input')
|
|||||||
var lay_ctx = layout.make_context();
|
var lay_ctx = layout.make_context();
|
||||||
|
|
||||||
var clay_base = {
|
var clay_base = {
|
||||||
font: undefined,
|
font: null,
|
||||||
background_image: undefined,
|
background_image: null,
|
||||||
slice: 0,
|
slice: 0,
|
||||||
font: 'smalle.16',
|
font: 'smalle.16',
|
||||||
font_size: undefined,
|
font_size: null,
|
||||||
color: [1,1,1,1],
|
color: [1,1,1,1],
|
||||||
spacing:0,
|
spacing:0,
|
||||||
padding:0,
|
padding:0,
|
||||||
margin:0,
|
margin:0,
|
||||||
offset:[0,0],
|
offset:[0,0],
|
||||||
size:undefined,
|
size:null,
|
||||||
background_color: undefined
|
background_color: null
|
||||||
};
|
};
|
||||||
|
|
||||||
var root_item;
|
var root_item;
|
||||||
@@ -125,8 +125,8 @@ function add_item(config)
|
|||||||
// Adjust for child_gap
|
// Adjust for child_gap
|
||||||
if (root_config._childIndex > 0) {
|
if (root_config._childIndex > 0) {
|
||||||
var parentContain = root_config.contain || 0;
|
var parentContain = root_config.contain || 0;
|
||||||
var isVStack = (parentContain & layout.contain.column) !== 0;
|
var isVStack = (parentContain & layout.contain.column) != 0;
|
||||||
var isHStack = (parentContain & layout.contain.row) !== 0;
|
var isHStack = (parentContain & layout.contain.row) != 0;
|
||||||
|
|
||||||
if (isVStack) {
|
if (isVStack) {
|
||||||
margin.t += childGap;
|
margin.t += childGap;
|
||||||
@@ -162,7 +162,7 @@ function add_item(config)
|
|||||||
|
|
||||||
function rectify_configs(config_array)
|
function rectify_configs(config_array)
|
||||||
{
|
{
|
||||||
if (config_array.length === 0)
|
if (config_array.length == 0)
|
||||||
config_array = [{}];
|
config_array = [{}];
|
||||||
|
|
||||||
for (var i = config_array.length-1; i > 0; i--)
|
for (var i = config_array.length-1; i > 0; i--)
|
||||||
@@ -216,8 +216,8 @@ clay.button = function button(str, action, config = {})
|
|||||||
config.action = action;
|
config.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hovered = undefined;
|
var hovered = null;
|
||||||
clay.newframe = function() { hovered = undefined; }
|
clay.newframe = function() { hovered = null; }
|
||||||
|
|
||||||
// mousepos given in hud coordinates
|
// mousepos given in hud coordinates
|
||||||
clay.draw_commands = function draw_commands(cmds, pos = [0,0], mousepos = prosperon.camera.screen2hud(input.mouse.screenpos()))
|
clay.draw_commands = function draw_commands(cmds, pos = [0,0], mousepos = prosperon.camera.screen2hud(input.mouse.screenpos()))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
function tohex(n) {
|
function tohex(n) {
|
||||||
var s = Math.floor(n).toString(16);
|
var s = Math.floor(n).toString(16);
|
||||||
if (s.length === 1) s = "0" + s;
|
if (s.length == 1) s = "0" + s;
|
||||||
return s.toUpperCase();
|
return s.toUpperCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,14 +98,14 @@ Color.normalize = function (c) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (var p of Object.keys(c)) {
|
for (var p of Object.keys(c)) {
|
||||||
if (typeof c[p] !== "object") continue;
|
if (typeof c[p] != "object") continue;
|
||||||
if (!Array.isArray(c[p])) {
|
if (!Array.isArray(c[p])) {
|
||||||
Color.normalize(c[p]);
|
Color.normalize(c[p]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add alpha channel if not present
|
// Add alpha channel if not present
|
||||||
if (c[p].length === 3) {
|
if (c[p].length == 3) {
|
||||||
c[p][3] = 1;
|
c[p][3] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
var input = use('input')
|
var input = use('input')
|
||||||
var util = use('util')
|
|
||||||
|
|
||||||
var downkeys = {};
|
var downkeys = {};
|
||||||
|
|
||||||
@@ -108,9 +107,9 @@ input.mouse.normal.doc = "Set the mouse to show again after hiding.";
|
|||||||
|
|
||||||
input.keyboard = {};
|
input.keyboard = {};
|
||||||
input.keyboard.down = function (code) {
|
input.keyboard.down = function (code) {
|
||||||
if (typeof code === "number") return downkeys[code];
|
if (typeof code == "number") return downkeys[code];
|
||||||
if (typeof code === "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
|
if (typeof code == "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
|
||||||
return undefined;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
input.print_pawn_kbm = function (pawn) {
|
input.print_pawn_kbm = function (pawn) {
|
||||||
@@ -158,7 +157,7 @@ input.print_md_kbm = function print_md_kbm(pawn) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
input.has_bind = function (pawn, bind) {
|
input.has_bind = function (pawn, bind) {
|
||||||
return typeof pawn.inputs?.[bind] === "function";
|
return typeof pawn.inputs?.[bind] == "function";
|
||||||
};
|
};
|
||||||
|
|
||||||
input.action = {
|
input.action = {
|
||||||
@@ -177,17 +176,17 @@ input.tabcomplete = function tabcomplete(val, list) {
|
|||||||
if (!val) return val;
|
if (!val) return val;
|
||||||
list = filter(x => x.startsWith(val))
|
list = filter(x => x.startsWith(val))
|
||||||
|
|
||||||
if (list.length === 1) {
|
if (list.length == 1) {
|
||||||
return list[0];
|
return list[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = undefined;
|
var ret = null;
|
||||||
var i = val.length;
|
var i = val.length;
|
||||||
while (!ret && list.length !== 0) {
|
while (!ret && list.length != 0) {
|
||||||
var char = list[0][i];
|
var char = list[0][i];
|
||||||
if (
|
if (
|
||||||
!list.every(function (x) {
|
!list.every(function (x) {
|
||||||
return x[i] === char;
|
return x[i] == char;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
ret = list[0].slice(0, i);
|
ret = list[0].slice(0, i);
|
||||||
@@ -214,7 +213,7 @@ var Player = {
|
|||||||
|
|
||||||
mouse_input(type, ...args) {
|
mouse_input(type, ...args) {
|
||||||
for (var pawn of [...this.pawns].reverse()) {
|
for (var pawn of [...this.pawns].reverse()) {
|
||||||
if (typeof pawn.inputs?.mouse?.[type] === "function") {
|
if (typeof pawn.inputs?.mouse?.[type] == "function") {
|
||||||
pawn.inputs.mouse[type].call(pawn, ...args);
|
pawn.inputs.mouse[type].call(pawn, ...args);
|
||||||
pawn.inputs.post?.call(pawn);
|
pawn.inputs.post?.call(pawn);
|
||||||
if (!pawn.inputs.fallthru) return;
|
if (!pawn.inputs.fallthru) return;
|
||||||
@@ -224,7 +223,7 @@ var Player = {
|
|||||||
|
|
||||||
char_input(c) {
|
char_input(c) {
|
||||||
for (var pawn of [...this.pawns].reverse()) {
|
for (var pawn of [...this.pawns].reverse()) {
|
||||||
if (typeof pawn.inputs?.char === "function") {
|
if (typeof pawn.inputs?.char == "function") {
|
||||||
pawn.inputs.char.call(pawn, c);
|
pawn.inputs.char.call(pawn, c);
|
||||||
pawn.inputs.post?.call(pawn);
|
pawn.inputs.post?.call(pawn);
|
||||||
if (!pawn.inputs.fallthru) return;
|
if (!pawn.inputs.fallthru) return;
|
||||||
@@ -271,16 +270,16 @@ var Player = {
|
|||||||
fn = inputs[cmd].released;
|
fn = inputs[cmd].released;
|
||||||
break;
|
break;
|
||||||
case "down":
|
case "down":
|
||||||
if (typeof inputs[cmd].down === "function") fn = inputs[cmd].down;
|
if (typeof inputs[cmd].down == "function") fn = inputs[cmd].down;
|
||||||
else if (inputs[cmd].down) fn = inputs[cmd];
|
else if (inputs[cmd].down) fn = inputs[cmd];
|
||||||
}
|
}
|
||||||
|
|
||||||
var consumed = false;
|
var consumed = false;
|
||||||
if (typeof fn === "function") {
|
if (typeof fn == "function") {
|
||||||
fn.call(pawn, ...args);
|
fn.call(pawn, ...args);
|
||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
if (state === "released") inputs.release_post?.call(pawn);
|
if (state == "released") inputs.release_post?.call(pawn);
|
||||||
if (inputs.block) return;
|
if (inputs.block) return;
|
||||||
if (consumed) return;
|
if (consumed) return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,62 +7,22 @@ A collection of 2D drawing functions that create drawing command lists.
|
|||||||
These are pure functions that return plain JavaScript objects representing
|
These are pure functions that return plain JavaScript objects representing
|
||||||
drawing operations. No rendering or actor communication happens here.
|
drawing operations. No rendering or actor communication happens here.
|
||||||
`
|
`
|
||||||
|
var current_list = []
|
||||||
// Create a new command list
|
|
||||||
draw.list = function() {
|
|
||||||
var commands = []
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Add a command to this list
|
|
||||||
push: function(cmd) {
|
|
||||||
commands.push(cmd)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get all commands
|
|
||||||
get: function() {
|
|
||||||
return commands
|
|
||||||
},
|
|
||||||
|
|
||||||
// Clear all commands
|
|
||||||
clear: function() {
|
|
||||||
commands = []
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get command count
|
|
||||||
length: function() {
|
|
||||||
return commands.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default command list for convenience
|
|
||||||
var current_list = draw.list()
|
|
||||||
|
|
||||||
// Set the current list
|
|
||||||
draw.set_list = function(list) {
|
|
||||||
current_list = list
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current list
|
|
||||||
draw.get_list = function() {
|
|
||||||
return current_list
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear current list
|
// Clear current list
|
||||||
draw.clear = function() {
|
draw.clear = function() {
|
||||||
current_list.clear()
|
current_list.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get commands from current list
|
// Get commands from current list
|
||||||
draw.get_commands = function() {
|
draw.get_commands = function() {
|
||||||
return current_list.get()
|
return current_list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to add a command
|
// Helper to add a command
|
||||||
function add_command(type, data) {
|
function add_command(type, data) {
|
||||||
var cmd = {cmd: type}
|
data.cmd = type
|
||||||
Object.assign(cmd, data)
|
current_list.push(data)
|
||||||
current_list.push(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default geometry definitions
|
// Default geometry definitions
|
||||||
@@ -115,16 +75,9 @@ draw.point = function(pos, size, opt = {}, material) {
|
|||||||
material: material
|
material: material
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
draw.point[cell.DOC] = `
|
|
||||||
:param pos: A 2D position ([x, y]) where the point should be drawn.
|
|
||||||
:param size: The size of the point.
|
|
||||||
:param opt: Optional geometry properties.
|
|
||||||
:param material: Material/styling information (color, shaders, etc.)
|
|
||||||
:return: None
|
|
||||||
`
|
|
||||||
|
|
||||||
draw.ellipse = function(pos, radii, def, material) {
|
draw.ellipse = function(pos, radii, defl, material) {
|
||||||
var opt = def ? {...ellipse_def, ...def} : ellipse_def
|
var opt = defl ? {...ellipse_def, ...defl} : ellipse_def
|
||||||
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
|
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
|
||||||
|
|
||||||
add_command("draw_ellipse", {
|
add_command("draw_ellipse", {
|
||||||
@@ -135,9 +88,9 @@ draw.ellipse = function(pos, radii, def, material) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.line = function(points, def, material)
|
draw.line = function(points, defl, material)
|
||||||
{
|
{
|
||||||
var opt = def ? {...line_def, ...def} : line_def
|
var opt = defl ? {...line_def, ...defl} : line_def
|
||||||
|
|
||||||
add_command("draw_line", {
|
add_command("draw_line", {
|
||||||
points: points,
|
points: points,
|
||||||
@@ -146,24 +99,24 @@ draw.line = function(points, def, material)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.cross = function render_cross(pos, size, def, material) {
|
draw.cross = function render_cross(pos, size, defl, material) {
|
||||||
var a = [pos.add([0, size]), pos.add([0, -size])]
|
var a = [pos.add([0, size]), pos.add([0, -size])]
|
||||||
var b = [pos.add([size, 0]), pos.add([-size, 0])]
|
var b = [pos.add([size, 0]), pos.add([-size, 0])]
|
||||||
draw.line(a, def, material)
|
draw.line(a, defl, material)
|
||||||
draw.line(b, def, material)
|
draw.line(b, defl, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, def, material) {
|
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, defl, material) {
|
||||||
var dir = math.norm(end.sub(start))
|
var dir = math.norm(end.sub(start))
|
||||||
var wing1 = [math.rotate(dir, wingangle).scale(wingspan).add(end), end]
|
var wing1 = [math.rotate(dir, wingangle).scale(wingspan).add(end), end]
|
||||||
var wing2 = [math.rotate(dir, -wingangle).scale(wingspan).add(end), end]
|
var wing2 = [math.rotate(dir, -wingangle).scale(wingspan).add(end), end]
|
||||||
draw.line([start, end], def, material)
|
draw.line([start, end], defl, material)
|
||||||
draw.line(wing1, def, material)
|
draw.line(wing1, defl, material)
|
||||||
draw.line(wing2, def, material)
|
draw.line(wing2, defl, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.rectangle = function render_rectangle(rect, def, material) {
|
draw.rectangle = function render_rectangle(rect, defl, material) {
|
||||||
var opt = def ? {...rect_def, ...def} : rect_def
|
var opt = defl ? {...rect_def, ...defl} : rect_def
|
||||||
|
|
||||||
add_command("draw_rect", {
|
add_command("draw_rect", {
|
||||||
rect: rect,
|
rect: rect,
|
||||||
@@ -184,27 +137,25 @@ draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.image = function image(image, rect, rotation = 0, anchor = [0,0], shear = [0,0], info = {}, material) {
|
draw.image = function image(image, rect, rotation, anchor, shear, info, material) {
|
||||||
if (!rect) throw Error('Need rectangle to render image.')
|
if (!rect) throw Error('Need rectangle to render image.')
|
||||||
if (!image) throw Error('Need an image to render.')
|
if (!image) throw Error('Need an image to render.')
|
||||||
|
|
||||||
if (!('x' in rect && 'y' in rect)) throw Error('Must provide X and Y for image.')
|
if (!('x' in rect && 'y' in rect)) throw Error('Must provide X and Y for image.')
|
||||||
|
|
||||||
info = Object.assign({}, image_info, info);
|
|
||||||
|
|
||||||
add_command("draw_image", {
|
add_command("draw_image", {
|
||||||
image: image,
|
image,
|
||||||
rect: rect,
|
rect,
|
||||||
rotation: rotation,
|
rotation,
|
||||||
anchor: anchor,
|
anchor,
|
||||||
shear: shear,
|
shear,
|
||||||
info: info,
|
info,
|
||||||
material: material
|
material,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.circle = function render_circle(pos, radius, def, material) {
|
draw.circle = function render_circle(pos, radius, defl, material) {
|
||||||
draw.ellipse(pos, [radius,radius], def, material)
|
draw.ellipse(pos, [radius,radius], defl, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.text = function text(text, pos, font = 'fonts/c64.ttf', size = 8, color = color.white, wrap = 0) {
|
draw.text = function text(text, pos, font = 'fonts/c64.ttf', size = 8, color = color.white, wrap = 0) {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ ex.draw = function()
|
|||||||
if (!diff) throw new Error("emitter does not have a proper diffuse texture")
|
if (!diff) throw new Error("emitter does not have a proper diffuse texture")
|
||||||
|
|
||||||
var mesh = graphics.make_sprite_mesh(this.particles)
|
var mesh = graphics.make_sprite_mesh(this.particles)
|
||||||
if (mesh.num_indices === 0) return
|
if (mesh.num_indices == 0) return
|
||||||
render.queue({
|
render.queue({
|
||||||
type:'geometry',
|
type:'geometry',
|
||||||
mesh,
|
mesh,
|
||||||
|
|||||||
@@ -66,5 +66,5 @@ this.hud = function() {
|
|||||||
draw.images(bunnyTex, bunnies)
|
draw.images(bunnyTex, bunnies)
|
||||||
|
|
||||||
var msg = 'FPS: ' + fpsAvg.toFixed(2) + ' Bunnies: ' + bunnies.length
|
var msg = 'FPS: ' + fpsAvg.toFixed(2) + ' Bunnies: ' + bunnies.length
|
||||||
draw.text(msg, {x:0, y:0, width:config.width, height:40}, undefined, 0, color.white, 0)
|
draw.text(msg, {x:0, y:0, width:config.width, height:40}, null, 0, color.white, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function updateTitle() {
|
|||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
if (myColor) {
|
if (myColor) {
|
||||||
title += (mover.turn === myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
|
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
|
||||||
} else {
|
} else {
|
||||||
title += mover.turn + " turn";
|
title += mover.turn + " turn";
|
||||||
}
|
}
|
||||||
@@ -64,10 +64,10 @@ var opponentHoldingPiece = false;
|
|||||||
var opponentSelectPos = null;
|
var opponentSelectPos = null;
|
||||||
|
|
||||||
function handleMouseButtonDown(e) {
|
function handleMouseButtonDown(e) {
|
||||||
if (e.which !== 0) return;
|
if (e.which != 0) return;
|
||||||
|
|
||||||
// Don't allow piece selection unless we have an opponent
|
// Don't allow piece selection unless we have an opponent
|
||||||
if (gameState !== 'connected' || !opponent) return;
|
if (gameState != 'connected' || !opponent) return;
|
||||||
|
|
||||||
var mx = e.mouse.x;
|
var mx = e.mouse.x;
|
||||||
var my = e.mouse.y;
|
var my = e.mouse.y;
|
||||||
@@ -76,7 +76,7 @@ function handleMouseButtonDown(e) {
|
|||||||
if (!grid.inBounds(c)) return;
|
if (!grid.inBounds(c)) return;
|
||||||
|
|
||||||
var cell = grid.at(c);
|
var cell = grid.at(c);
|
||||||
if (cell.length && cell[0].colour === mover.turn) {
|
if (cell.length && cell[0].colour == mover.turn) {
|
||||||
selectPos = c;
|
selectPos = c;
|
||||||
holdingPiece = true;
|
holdingPiece = true;
|
||||||
// Send pickup notification to opponent
|
// Send pickup notification to opponent
|
||||||
@@ -92,10 +92,10 @@ function handleMouseButtonDown(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseButtonUp(e) {
|
function handleMouseButtonUp(e) {
|
||||||
if (e.which !== 0 || !holdingPiece || !selectPos) return;
|
if (e.which != 0 || !holdingPiece || !selectPos) return;
|
||||||
|
|
||||||
// Don't allow moves unless we have an opponent and it's our turn
|
// Don't allow moves unless we have an opponent and it's our turn
|
||||||
if (gameState !== 'connected' || !opponent || !isMyTurn) {
|
if (gameState != 'connected' || !opponent || !isMyTurn) {
|
||||||
holdingPiece = false;
|
holdingPiece = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ function handleMouseMotion(e) {
|
|||||||
hoverPos = c;
|
hoverPos = c;
|
||||||
|
|
||||||
// Send mouse position to opponent in real-time
|
// Send mouse position to opponent in real-time
|
||||||
if (opponent && gameState === 'connected') {
|
if (opponent && gameState == 'connected') {
|
||||||
send(opponent, {
|
send(opponent, {
|
||||||
type: 'mouse_move',
|
type: 'mouse_move',
|
||||||
pos: c,
|
pos: c,
|
||||||
@@ -159,11 +159,11 @@ function handleMouseMotion(e) {
|
|||||||
|
|
||||||
function handleKeyDown(e) {
|
function handleKeyDown(e) {
|
||||||
// S key - start server
|
// S key - start server
|
||||||
if (e.scancode === 22 && gameState === 'waiting') { // S key
|
if (e.scancode == 22 && gameState == 'waiting') { // S key
|
||||||
startServer();
|
startServer();
|
||||||
}
|
}
|
||||||
// J key - join server
|
// J key - join server
|
||||||
else if (e.scancode === 13 && gameState === 'waiting') { // J key
|
else if (e.scancode == 13 && gameState == 'waiting') { // J key
|
||||||
joinServer();
|
joinServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,8 +181,8 @@ var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
|
|||||||
function drawBoard() {
|
function drawBoard() {
|
||||||
for (var y = 0; y < 8; ++y)
|
for (var y = 0; y < 8; ++y)
|
||||||
for (var x = 0; x < 8; ++x) {
|
for (var x = 0; x < 8; ++x) {
|
||||||
var isMyHover = hoverPos && hoverPos[0] === x && hoverPos[1] === y;
|
var isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
|
||||||
var isOpponentHover = opponentMousePos && opponentMousePos[0] === x && opponentMousePos[1] === y;
|
var isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
|
||||||
var isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
|
var isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
|
||||||
|
|
||||||
var color = ((x+y)&1) ? dark : light;
|
var color = ((x+y)&1) ? dark : light;
|
||||||
@@ -211,7 +211,7 @@ function isValidMoveForTurn(from, to) {
|
|||||||
|
|
||||||
// Check if the destination has a piece of the same color
|
// Check if the destination has a piece of the same color
|
||||||
var destCell = grid.at(to);
|
var destCell = grid.at(to);
|
||||||
if (destCell.length && destCell[0].colour === piece.colour) {
|
if (destCell.length && destCell[0].colour == piece.colour) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,15 +225,15 @@ function drawPieces() {
|
|||||||
|
|
||||||
// Skip drawing the piece being held (by me or opponent)
|
// Skip drawing the piece being held (by me or opponent)
|
||||||
if (holdingPiece && selectPos &&
|
if (holdingPiece && selectPos &&
|
||||||
piece.coord[0] === selectPos[0] &&
|
piece.coord[0] == selectPos[0] &&
|
||||||
piece.coord[1] === selectPos[1]) {
|
piece.coord[1] == selectPos[1]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip drawing the piece being held by opponent
|
// Skip drawing the piece being held by opponent
|
||||||
if (opponentHoldingPiece && opponentSelectPos &&
|
if (opponentHoldingPiece && opponentSelectPos &&
|
||||||
piece.coord[0] === opponentSelectPos[0] &&
|
piece.coord[0] == opponentSelectPos[0] &&
|
||||||
piece.coord[1] === opponentSelectPos[1]) {
|
piece.coord[1] == opponentSelectPos[1]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,11 +277,9 @@ function draw()
|
|||||||
draw2d.clear()
|
draw2d.clear()
|
||||||
drawBoard()
|
drawBoard()
|
||||||
drawPieces()
|
drawPieces()
|
||||||
draw2d.text("HELL", {x: 100, y: 100}, 'fonts/c64.ttf', 16, [1,1,1,1])
|
|
||||||
return draw2d.get_commands()
|
return draw2d.get_commands()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function startServer() {
|
function startServer() {
|
||||||
gameState = 'server_waiting';
|
gameState = 'server_waiting';
|
||||||
isServer = true;
|
isServer = true;
|
||||||
@@ -331,10 +329,10 @@ $_.receiver(e => {
|
|||||||
send(e, update(e.dt))
|
send(e, update(e.dt))
|
||||||
else if (e.kind == 'draw')
|
else if (e.kind == 'draw')
|
||||||
send(e, draw())
|
send(e, draw())
|
||||||
else if (e.type === 'game_start' || e.type === 'move' || e.type === 'greet')
|
else if (e.type == 'game_start' || e.type == 'move' || e.type == 'greet')
|
||||||
log.console("Receiver got message:", e.type, e);
|
log.console("Receiver got message:", e.type, e);
|
||||||
|
|
||||||
if (e.type === 'greet') {
|
if (e.type == 'greet') {
|
||||||
log.console("Server received greet from client");
|
log.console("Server received greet from client");
|
||||||
// Store the client's actor object for ongoing communication
|
// Store the client's actor object for ongoing communication
|
||||||
opponent = e.client_actor;
|
opponent = e.client_actor;
|
||||||
@@ -350,13 +348,13 @@ $_.receiver(e => {
|
|||||||
});
|
});
|
||||||
log.console("game_start message sent to client");
|
log.console("game_start message sent to client");
|
||||||
}
|
}
|
||||||
else if (e.type === 'game_start') {
|
else if (e.type == 'game_start') {
|
||||||
log.console("Game starting, I am:", e.your_color);
|
log.console("Game starting, I am:", e.your_color);
|
||||||
myColor = e.your_color;
|
myColor = e.your_color;
|
||||||
isMyTurn = (myColor === 'white');
|
isMyTurn = (myColor == 'white');
|
||||||
gameState = 'connected';
|
gameState = 'connected';
|
||||||
updateTitle();
|
updateTitle();
|
||||||
} else if (e.type === 'move') {
|
} else if (e.type == 'move') {
|
||||||
log.console("Received move from opponent:", e.from, "to", e.to);
|
log.console("Received move from opponent:", e.from, "to", e.to);
|
||||||
// Apply opponent's move
|
// Apply opponent's move
|
||||||
var fromCell = grid.at(e.from);
|
var fromCell = grid.at(e.from);
|
||||||
@@ -372,26 +370,26 @@ $_.receiver(e => {
|
|||||||
} else {
|
} else {
|
||||||
log.console("No piece found at from position");
|
log.console("No piece found at from position");
|
||||||
}
|
}
|
||||||
} else if (e.type === 'mouse_move') {
|
} else if (e.type == 'mouse_move') {
|
||||||
// Update opponent's mouse position
|
// Update opponent's mouse position
|
||||||
opponentMousePos = e.pos;
|
opponentMousePos = e.pos;
|
||||||
opponentHoldingPiece = e.holding;
|
opponentHoldingPiece = e.holding;
|
||||||
opponentSelectPos = e.selectPos;
|
opponentSelectPos = e.selectPos;
|
||||||
} else if (e.type === 'piece_pickup') {
|
} else if (e.type == 'piece_pickup') {
|
||||||
// Opponent picked up a piece
|
// Opponent picked up a piece
|
||||||
opponentSelectPos = e.pos;
|
opponentSelectPos = e.pos;
|
||||||
opponentHoldingPiece = true;
|
opponentHoldingPiece = true;
|
||||||
} else if (e.type === 'piece_drop') {
|
} else if (e.type == 'piece_drop') {
|
||||||
// Opponent dropped their piece
|
// Opponent dropped their piece
|
||||||
opponentHoldingPiece = false;
|
opponentHoldingPiece = false;
|
||||||
opponentSelectPos = null;
|
opponentSelectPos = null;
|
||||||
} else if (e.type === 'mouse_button_down') {
|
} else if (e.type == 'mouse_button_down') {
|
||||||
handleMouseButtonDown(e)
|
handleMouseButtonDown(e)
|
||||||
} else if (e.type === 'mouse_button_up') {
|
} else if (e.type == 'mouse_button_up') {
|
||||||
handleMouseButtonUp(e)
|
handleMouseButtonUp(e)
|
||||||
} else if (e.type === 'mouse_motion') {
|
} else if (e.type == 'mouse_motion') {
|
||||||
handleMouseMotion(e)
|
handleMouseMotion(e)
|
||||||
} else if (e.type === 'key_down') {
|
} else if (e.type == 'key_down') {
|
||||||
handleKeyDown(e)
|
handleKeyDown(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,57 +1,69 @@
|
|||||||
var CELLS = Symbol()
|
function grid(w, h) {
|
||||||
|
this.width = w;
|
||||||
var key = function key(x,y) { return `${x},${y}` }
|
|
||||||
|
|
||||||
function grid(w, h)
|
|
||||||
{
|
|
||||||
this[CELLS] = new Map()
|
|
||||||
this.width = w;
|
|
||||||
this.height = h;
|
this.height = h;
|
||||||
|
// create a height×width array of empty lists
|
||||||
|
this.cells = new Array(h);
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
this.cells[y] = new Array(w);
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
this.cells[y][x] = []; // each cell holds its own list
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
grid.prototype = {
|
grid.prototype = {
|
||||||
cell(x,y) {
|
// return the array at (x,y)
|
||||||
var k = key(x,y)
|
cell(x, y) {
|
||||||
if (!this[CELLS].has(k)) this[CELLS].set(k,[])
|
return this.cells[y][x];
|
||||||
return this[CELLS].get(k)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// alias for cell
|
||||||
|
at(pos) {
|
||||||
|
return this.cell(pos.x, pos.y);
|
||||||
|
},
|
||||||
|
|
||||||
|
// add an entity into a cell
|
||||||
add(entity, pos) {
|
add(entity, pos) {
|
||||||
this.cell(pos.x, pos.y).push(entity);
|
this.cell(pos.x, pos.y).push(entity);
|
||||||
entity.coord = pos.slice();
|
entity.coord = pos.slice();
|
||||||
},
|
},
|
||||||
|
|
||||||
remove(entity, pos) {
|
|
||||||
var c = this.cell(pos.x, pos.y);
|
|
||||||
c.splice(c.indexOf(entity), 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
at(pos) {
|
|
||||||
return this.cell(pos.x, pos.y);
|
|
||||||
},
|
|
||||||
|
|
||||||
inBounds(pos) {
|
|
||||||
return pos.x >= 0 && pos.x < this.width && pos.y >= 0 && pos.y < this.height;
|
|
||||||
},
|
|
||||||
|
|
||||||
each(fn) {
|
|
||||||
for (var [k, list] of this[CELLS])
|
|
||||||
for (var p of list) fn(p, p.coord);
|
|
||||||
},
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
var out = `grid [${this.width}x${this.height}]
|
|
||||||
`
|
|
||||||
for (var y = 0; y < this.height; y++) {
|
|
||||||
for (var x = 0; x < this.width; x++) {
|
|
||||||
var cell = this.at([x,y]);
|
|
||||||
out += cell.length
|
|
||||||
}
|
|
||||||
if (y !== this.height - 1) out += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return grid
|
// remove an entity from a cell
|
||||||
|
remove(entity, pos) {
|
||||||
|
const c = this.cell(pos.x, pos.y);
|
||||||
|
const i = c.indexOf(entity);
|
||||||
|
if (i !== -1) c.splice(i, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
// bounds check
|
||||||
|
inBounds(pos) {
|
||||||
|
return (
|
||||||
|
pos.x >= 0 && pos.x < this.width &&
|
||||||
|
pos.y >= 0 && pos.y < this.height
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// call fn(entity, coord) for every entity in every cell
|
||||||
|
each(fn) {
|
||||||
|
for (let y = 0; y < this.height; y++) {
|
||||||
|
for (let x = 0; x < this.width; x++) {
|
||||||
|
const list = this.cells[y][x];
|
||||||
|
for (let entity of list) {
|
||||||
|
fn(entity, entity.coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// printable representation
|
||||||
|
toString() {
|
||||||
|
let out = `grid [${this.width}×${this.height}]\n`;
|
||||||
|
for (let y = 0; y < this.height; y++) {
|
||||||
|
for (let x = 0; x < this.width; x++) {
|
||||||
|
out += this.cells[y][x].length;
|
||||||
|
}
|
||||||
|
if (y !== this.height - 1) out += "\n";
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ var MovementSystem = function(grid, rules) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MovementSystem.prototype.tryMove = function (piece, to) {
|
MovementSystem.prototype.tryMove = function (piece, to) {
|
||||||
if (piece.colour !== this.turn) return false;
|
if (piece.colour != this.turn) return false;
|
||||||
|
|
||||||
// normalise ‘to’ into our hybrid coord
|
// normalise ‘to’ into our hybrid coord
|
||||||
var dest = [to.x !== undefined ? to.x : to[0],
|
var dest = [to.x ?? t[0],
|
||||||
to.y !== undefined ? to.y : to[1]];
|
to.y ?? to[1]];
|
||||||
|
|
||||||
if (!this.grid.inBounds(dest)) return false;
|
if (!this.grid.inBounds(dest)) return false;
|
||||||
if (!this.rules.canMove(piece, piece.coord, dest, this.grid)) return false;
|
if (!this.rules.canMove(piece, piece.coord, dest, this.grid)) return false;
|
||||||
|
|
||||||
var victims = this.grid.at(dest);
|
var victims = this.grid.at(dest);
|
||||||
if (victims.length && victims[0].colour === piece.colour) return false;
|
if (victims.length && victims[0].colour == piece.colour) return false;
|
||||||
if (victims.length) victims[0].captured = true;
|
if (victims.length) victims[0].captured = true;
|
||||||
|
|
||||||
this.grid.remove(piece, piece.coord);
|
this.grid.remove(piece, piece.coord);
|
||||||
@@ -25,7 +25,7 @@ MovementSystem.prototype.tryMove = function (piece, to) {
|
|||||||
piece.coord.x = dest.x;
|
piece.coord.x = dest.x;
|
||||||
piece.coord.y = dest.y;
|
piece.coord.y = dest.y;
|
||||||
|
|
||||||
this.turn = (this.turn === 'white') ? 'black' : 'white';
|
this.turn = (this.turn == 'white') ? 'black' : 'white';
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
/* helper – robust coord access */
|
/* helper – robust coord access */
|
||||||
function cx(c) { return (c.x !== undefined) ? c.x : c[0]; }
|
function cx(c) { return c.x ?? c[0] }
|
||||||
function cy(c) { return (c.y !== undefined) ? c.y : c[1]; }
|
function cy(c) { return c.y ?? c[1] }
|
||||||
|
|
||||||
/* simple move-shape checks */
|
/* simple move-shape checks */
|
||||||
var deltas = {
|
var deltas = {
|
||||||
pawn: function (pc, dx, dy, grid, to) {
|
pawn: function (pc, dx, dy, grid, to) {
|
||||||
var dir = (pc.colour === 'white') ? -1 : 1;
|
var dir = (pc.colour == 'white') ? -1 : 1;
|
||||||
var base = (pc.colour === 'white') ? 6 : 1;
|
var base = (pc.colour == 'white') ? 6 : 1;
|
||||||
var one = (dy === dir && dx === 0 && grid.at(to).length === 0);
|
var one = (dy == dir && dx == 0 && grid.at(to).length == 0);
|
||||||
var two = (dy === 2 * dir && dx === 0 && cy(pc.coord) === base &&
|
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
|
||||||
grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir }).length === 0 &&
|
grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir }).length == 0 &&
|
||||||
grid.at(to).length === 0);
|
grid.at(to).length == 0);
|
||||||
var cap = (dy === dir && Math.abs(dx) === 1 && grid.at(to).length);
|
var cap = (dy == dir && Math.abs(dx) == 1 && grid.at(to).length);
|
||||||
return one || two || cap;
|
return one || two || cap;
|
||||||
},
|
},
|
||||||
rook : function (pc, dx, dy) { return (dx === 0 || dy === 0); },
|
rook : function (pc, dx, dy) { return (dx == 0 || dy == 0); },
|
||||||
bishop: function (pc, dx, dy) { return Math.abs(dx) === Math.abs(dy); },
|
bishop: function (pc, dx, dy) { return Math.abs(dx) == Math.abs(dy); },
|
||||||
queen : function (pc, dx, dy) { return (dx === 0 || dy === 0 || Math.abs(dx) === Math.abs(dy)); },
|
queen : function (pc, dx, dy) { return (dx == 0 || dy == 0 || Math.abs(dx) == Math.abs(dy)); },
|
||||||
knight: function (pc, dx, dy) { return (Math.abs(dx) === 1 && Math.abs(dy) === 2) ||
|
knight: function (pc, dx, dy) { return (Math.abs(dx) == 1 && Math.abs(dy) == 2) ||
|
||||||
(Math.abs(dx) === 2 && Math.abs(dy) === 1); },
|
(Math.abs(dx) == 2 && Math.abs(dy) == 1); },
|
||||||
king : function (pc, dx, dy) { return Math.max(Math.abs(dx), Math.abs(dy)) === 1; }
|
king : function (pc, dx, dy) { return Math.max(Math.abs(dx), Math.abs(dy)) == 1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
function clearLine(from, to, grid) {
|
function clearLine(from, to, grid) {
|
||||||
var dx = Math.sign(cx(to) - cx(from));
|
var dx = Math.sign(cx(to) - cx(from));
|
||||||
var dy = Math.sign(cy(to) - cy(from));
|
var dy = Math.sign(cy(to) - cy(from));
|
||||||
var x = cx(from) + dx, y = cy(from) + dy;
|
var x = cx(from) + dx, y = cy(from) + dy;
|
||||||
while (x !== cx(to) || y !== cy(to)) {
|
while (x != cx(to) || y != cy(to)) {
|
||||||
if (grid.at({ x: x, y: y }).length) return false;
|
if (grid.at({ x: x, y: y }).length) return false;
|
||||||
x += dx; y += dy;
|
x += dx; y += dy;
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ function canMove(piece, from, to, grid) {
|
|||||||
var dy = cy(to) - cy(from);
|
var dy = cy(to) - cy(from);
|
||||||
var f = deltas[piece.kind];
|
var f = deltas[piece.kind];
|
||||||
if (!f || !f(piece, dx, dy, grid, to)) return false;
|
if (!f || !f(piece, dx, dy, grid, to)) return false;
|
||||||
if (piece.kind === 'knight') return true;
|
if (piece.kind == 'knight') return true;
|
||||||
return clearLine(from, to, grid);
|
return clearLine(from, to, grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,5 +82,5 @@ this.hud = function() {
|
|||||||
|
|
||||||
// Simple score display
|
// Simple score display
|
||||||
var msg = score1 + " " + score2
|
var msg = score1 + " " + score2
|
||||||
draw.text(msg, {x:0, y:10, width:config.width, height:40}, undefined, 0, color.white, 0)
|
draw.text(msg, {x:0, y:10, width:config.width, height:40}, null, 0, color.white, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function spawnApple() {
|
|||||||
apple = {x:Math.floor(Math.random()*gridW), y:Math.floor(Math.random()*gridH)}
|
apple = {x:Math.floor(Math.random()*gridW), y:Math.floor(Math.random()*gridH)}
|
||||||
// Re-spawn if apple lands on snake
|
// Re-spawn if apple lands on snake
|
||||||
for (var i=0; i<snake.length; i++)
|
for (var i=0; i<snake.length; i++)
|
||||||
if (snake[i].x === apple.x && snake[i].y === apple.y) { spawnApple(); return }
|
if (snake[i].x == apple.x && snake[i].y == apple.y) { spawnApple(); return }
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap(pos) {
|
function wrap(pos) {
|
||||||
@@ -49,7 +49,7 @@ function wrap(pos) {
|
|||||||
resetGame()
|
resetGame()
|
||||||
|
|
||||||
this.update = function(dt) {
|
this.update = function(dt) {
|
||||||
if (gameState !== "playing") return
|
if (gameState != "playing") return
|
||||||
moveTimer += dt
|
moveTimer += dt
|
||||||
if (moveTimer < moveInterval) return
|
if (moveTimer < moveInterval) return
|
||||||
moveTimer -= moveInterval
|
moveTimer -= moveInterval
|
||||||
@@ -63,7 +63,7 @@ this.update = function(dt) {
|
|||||||
|
|
||||||
// Check collision with body
|
// Check collision with body
|
||||||
for (var i=0; i<snake.length; i++) {
|
for (var i=0; i<snake.length; i++) {
|
||||||
if (snake[i].x === head.x && snake[i].y === head.y) {
|
if (snake[i].x == head.x && snake[i].y == head.y) {
|
||||||
gameState = "gameover"
|
gameState = "gameover"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ this.update = function(dt) {
|
|||||||
snake.unshift(head)
|
snake.unshift(head)
|
||||||
|
|
||||||
// Eat apple?
|
// Eat apple?
|
||||||
if (head.x === apple.x && head.y === apple.y) spawnApple()
|
if (head.x == apple.x && head.y == apple.y) spawnApple()
|
||||||
else snake.pop()
|
else snake.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +90,9 @@ this.hud = function() {
|
|||||||
// Draw apple
|
// Draw apple
|
||||||
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, color.red)
|
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, color.red)
|
||||||
|
|
||||||
if (gameState === "gameover") {
|
if (gameState == "gameover") {
|
||||||
var msg = "GAME OVER! Press SPACE to restart."
|
var msg = "GAME OVER! Press SPACE to restart."
|
||||||
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, undefined, 0, color.white)
|
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, null, 0, color.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,19 +100,19 @@ this.hud = function() {
|
|||||||
// "Up" means y=1, so going physically up on screen
|
// "Up" means y=1, so going physically up on screen
|
||||||
this.inputs = {
|
this.inputs = {
|
||||||
up: function() {
|
up: function() {
|
||||||
if (direction.y !== -1) nextDirection = {x:0,y:1}
|
if (direction.y != -1) nextDirection = {x:0,y:1}
|
||||||
},
|
},
|
||||||
down: function() {
|
down: function() {
|
||||||
if (direction.y !== 1) nextDirection = {x:0,y:-1}
|
if (direction.y != 1) nextDirection = {x:0,y:-1}
|
||||||
},
|
},
|
||||||
left: function() {
|
left: function() {
|
||||||
if (direction.x !== 1) nextDirection = {x:-1,y:0}
|
if (direction.x != 1) nextDirection = {x:-1,y:0}
|
||||||
},
|
},
|
||||||
right: function() {
|
right: function() {
|
||||||
if (direction.x !== -1) nextDirection = {x:1,y:0}
|
if (direction.x != -1) nextDirection = {x:1,y:0}
|
||||||
},
|
},
|
||||||
space: function() {
|
space: function() {
|
||||||
if (gameState==="gameover") resetGame()
|
if (gameState=="gameover") resetGame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ function end_game(score) {
|
|||||||
update_stat(STATS.TOTAL_SCORE, total_score, false);
|
update_stat(STATS.TOTAL_SCORE, total_score, false);
|
||||||
|
|
||||||
// Check for achievements
|
// Check for achievements
|
||||||
if (games_played === 1) {
|
if (games_played == 1) {
|
||||||
unlock_achievement(ACHIEVEMENTS.FIRST_WIN);
|
unlock_achievement(ACHIEVEMENTS.FIRST_WIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ function clearLines() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Score
|
// Score
|
||||||
if (lines===1) score += 100
|
if (lines==1) score += 100
|
||||||
else if (lines===2) score += 300
|
else if (lines==2) score += 300
|
||||||
else if (lines===3) score += 500
|
else if (lines==3) score += 500
|
||||||
else if (lines===4) score += 800
|
else if (lines==4) score += 800
|
||||||
linesCleared += lines
|
linesCleared += lines
|
||||||
level = Math.floor(linesCleared/10)
|
level = Math.floor(linesCleared/10)
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ spawnPiece()
|
|||||||
this.update = function(dt) {
|
this.update = function(dt) {
|
||||||
if (gameOver) return
|
if (gameOver) return
|
||||||
|
|
||||||
// ========== Horizontal Movement Gate ==========
|
// ======= Horizontal Movement Gate =======
|
||||||
var leftPressed = input.keyboard.down('a')
|
var leftPressed = input.keyboard.down('a')
|
||||||
var rightPressed = input.keyboard.down('d')
|
var rightPressed = input.keyboard.down('d')
|
||||||
var horizontalMove = 0
|
var horizontalMove = 0
|
||||||
@@ -191,7 +191,7 @@ this.update = function(dt) {
|
|||||||
hMoveTimer -= dt
|
hMoveTimer -= dt
|
||||||
prevLeft = leftPressed
|
prevLeft = leftPressed
|
||||||
prevRight = rightPressed
|
prevRight = rightPressed
|
||||||
// ========== End Horizontal Movement Gate ==========
|
// ======= End Horizontal Movement Gate =======
|
||||||
|
|
||||||
// Rotate with W (once per press, no spinning)
|
// Rotate with W (once per press, no spinning)
|
||||||
if (input.keyboard.down('w')) {
|
if (input.keyboard.down('w')) {
|
||||||
@@ -249,7 +249,7 @@ this.hud = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Next piece window
|
// Next piece window
|
||||||
draw.text("Next", {x:70, y:5, width:50, height:10}, undefined, 0, color.white)
|
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
|
||||||
if (nextPiece) {
|
if (nextPiece) {
|
||||||
for (var i=0; i<nextPiece.blocks.length; i++) {
|
for (var i=0; i<nextPiece.blocks.length; i++) {
|
||||||
var nx = nextPiece.blocks[i][0]
|
var nx = nextPiece.blocks[i][0]
|
||||||
@@ -262,10 +262,10 @@ this.hud = function() {
|
|||||||
|
|
||||||
// Score & Level
|
// Score & Level
|
||||||
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
|
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
|
||||||
draw.text(info, {x:70, y:30, width:90, height:50}, undefined, 0, color.white)
|
draw.text(info, {x:70, y:30, width:90, height:50}, null, 0, color.white)
|
||||||
|
|
||||||
if (gameOver) {
|
if (gameOver) {
|
||||||
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, undefined, 0, color.red)
|
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, null, 0, color.red)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ geometry.sphere.volume[cell.DOC] = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
geometry.sphere.random = function (r, theta = [0, 1], phi = [-0.5, 0.5]) {
|
geometry.sphere.random = function (r, theta = [0, 1], phi = [-0.5, 0.5]) {
|
||||||
if (typeof r === "number") r = [r, r]
|
if (typeof r == "number") r = [r, r]
|
||||||
if (typeof theta === "number") theta = [theta, theta]
|
if (typeof theta == "number") theta = [theta, theta]
|
||||||
if (typeof phi === "number") phi = [phi, phi]
|
if (typeof phi == "number") phi = [phi, phi]
|
||||||
|
|
||||||
var ra = Math.random_range(r[0], r[1])
|
var ra = Math.random_range(r[0], r[1])
|
||||||
var ta = Math.turn2rad(Math.random_range(theta[0], theta[1]))
|
var ta = Math.turn2rad(Math.random_range(theta[0], theta[1]))
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ rectangle packing, etc.
|
|||||||
`
|
`
|
||||||
|
|
||||||
var renderer_actor = arg[0]
|
var renderer_actor = arg[0]
|
||||||
var renderer_id = arg[1]
|
|
||||||
|
|
||||||
var io = use('io')
|
var io = use('io')
|
||||||
var time = use('time')
|
var time = use('time')
|
||||||
@@ -19,23 +18,22 @@ var CPU = Symbol()
|
|||||||
var LASTUSE = Symbol()
|
var LASTUSE = Symbol()
|
||||||
var LOADING = Symbol()
|
var LOADING = Symbol()
|
||||||
|
|
||||||
var cache = new Map()
|
var cache = {}
|
||||||
|
|
||||||
// Image constructor function
|
// Image constructor function
|
||||||
graphics.Image = function(surfaceData) {
|
graphics.Image = function(surfaceData) {
|
||||||
// Initialize private properties
|
// Initialize private properties
|
||||||
this[CPU] = surfaceData || undefined;
|
this[CPU] = surfaceData || null;
|
||||||
this[GPU] = undefined;
|
this[GPU] = null;
|
||||||
this[LOADING] = false;
|
this[LOADING] = false;
|
||||||
this[LASTUSE] = time.number();
|
this[LASTUSE] = time.number();
|
||||||
this.rect = {x:0, y:0, width:1, height:1};
|
this.rect = {x:0, y:0, width:surfaceData.width, height:surfaceData.height};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define getters and methods on the prototype
|
// Define getters and methods on the prototype
|
||||||
Object.defineProperties(graphics.Image.prototype, {
|
Object.defineProperties(graphics.Image.prototype, {
|
||||||
gpu: {
|
gpu: {
|
||||||
get: function() {
|
get: function() {
|
||||||
this[LASTUSE] = time.number();
|
|
||||||
if (!this[GPU] && !this[LOADING]) {
|
if (!this[GPU] && !this[LOADING]) {
|
||||||
this[LOADING] = true;
|
this[LOADING] = true;
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -43,7 +41,6 @@ Object.defineProperties(graphics.Image.prototype, {
|
|||||||
// Send message to load texture
|
// Send message to load texture
|
||||||
send(renderer_actor, {
|
send(renderer_actor, {
|
||||||
kind: "renderer",
|
kind: "renderer",
|
||||||
id: renderer_id,
|
|
||||||
op: "loadTexture",
|
op: "loadTexture",
|
||||||
data: this[CPU]
|
data: this[CPU]
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
@@ -69,9 +66,6 @@ Object.defineProperties(graphics.Image.prototype, {
|
|||||||
|
|
||||||
cpu: {
|
cpu: {
|
||||||
get: function() {
|
get: function() {
|
||||||
this[LASTUSE] = time.number();
|
|
||||||
// Note: Reading texture back from GPU requires async operation
|
|
||||||
// For now, return the CPU data if available
|
|
||||||
return this[CPU]
|
return this[CPU]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -95,11 +89,11 @@ Object.defineProperties(graphics.Image.prototype, {
|
|||||||
|
|
||||||
// Add methods to prototype
|
// Add methods to prototype
|
||||||
graphics.Image.prototype.unload_gpu = function() {
|
graphics.Image.prototype.unload_gpu = function() {
|
||||||
this[GPU] = undefined
|
this[GPU] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics.Image.prototype.unload_cpu = function() {
|
graphics.Image.prototype.unload_cpu = function() {
|
||||||
this[CPU] = undefined
|
this[CPU] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function calc_image_size(img) {
|
function calc_image_size(img) {
|
||||||
@@ -129,7 +123,7 @@ function make_handle(obj)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function wrapSurface(surf, maybeRect){
|
function wrapSurface(surf, maybeRect){
|
||||||
const h = make_handle(surf);
|
def h = make_handle(surf);
|
||||||
if(maybeRect) h.rect = maybeRect; /* honour frame sub-rect */
|
if(maybeRect) h.rect = maybeRect; /* honour frame sub-rect */
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
@@ -156,7 +150,7 @@ function decode_image(bytes, ext)
|
|||||||
|
|
||||||
function create_image(path){
|
function create_image(path){
|
||||||
try{
|
try{
|
||||||
const bytes = io.slurpbytes(path);
|
def bytes = io.slurpbytes(path);
|
||||||
|
|
||||||
let raw = decode_image(bytes, path.ext());
|
let raw = decode_image(bytes, path.ext());
|
||||||
|
|
||||||
@@ -175,8 +169,8 @@ function create_image(path){
|
|||||||
return makeAnim( wrapFrames(raw.frames), !!raw.loop );
|
return makeAnim( wrapFrames(raw.frames), !!raw.loop );
|
||||||
|
|
||||||
/* ── Case D: ASE helpers returned { animName:{frames,loop}, … } ── */
|
/* ── Case D: ASE helpers returned { animName:{frames,loop}, … } ── */
|
||||||
const anims = {};
|
def anims = {};
|
||||||
for(const [name, anim] of Object.entries(raw)){
|
for(def [name, anim] of Object.entries(raw)){
|
||||||
if(anim && Array.isArray(anim.frames))
|
if(anim && Array.isArray(anim.frames))
|
||||||
anims[name] = makeAnim( wrapFrames(anim.frames), !!anim.loop );
|
anims[name] = makeAnim( wrapFrames(anim.frames), !!anim.loop );
|
||||||
else if(anim && anim.surface) /* ase with flat surface */
|
else if(anim && anim.surface) /* ase with flat surface */
|
||||||
@@ -225,7 +219,7 @@ graphics.is_image[cell.DOC] = `
|
|||||||
|
|
||||||
graphics.texture_from_data = function(data)
|
graphics.texture_from_data = function(data)
|
||||||
{
|
{
|
||||||
if (!(data instanceof ArrayBuffer)) return undefined
|
if (!(data instanceof ArrayBuffer)) return null
|
||||||
|
|
||||||
var image = graphics.make_texture(data);
|
var image = graphics.make_texture(data);
|
||||||
var img = make_handle(image)
|
var img = make_handle(image)
|
||||||
@@ -242,7 +236,7 @@ graphics.from_surface = function(id, surf)
|
|||||||
|
|
||||||
graphics.from = function(id, data)
|
graphics.from = function(id, data)
|
||||||
{
|
{
|
||||||
if (typeof id !== 'string')
|
if (typeof id != 'string')
|
||||||
throw new Error('Expected a string ID')
|
throw new Error('Expected a string ID')
|
||||||
|
|
||||||
if (data instanceof ArrayBuffer)
|
if (data instanceof ArrayBuffer)
|
||||||
@@ -252,18 +246,18 @@ graphics.from = function(id, data)
|
|||||||
graphics.texture = function texture(path) {
|
graphics.texture = function texture(path) {
|
||||||
if (path instanceof graphics.Image) return path
|
if (path instanceof graphics.Image) return path
|
||||||
|
|
||||||
if (typeof path !== 'string')
|
if (typeof path != 'string')
|
||||||
throw new Error('need a string for graphics.texture')
|
throw new Error('need a string for graphics.texture')
|
||||||
|
|
||||||
var id = path.split(':')[0]
|
var id = path //.split(':')[0]
|
||||||
if (cache.has(id)) return cache.get(id)
|
if (cache[id]) return cache[id]
|
||||||
|
|
||||||
var ipath = res.find_image(id)
|
var ipath = res.find_image(id)
|
||||||
if (!ipath)
|
if (!ipath)
|
||||||
throw new Error(`unknown image ${id}`)
|
throw new Error(`unknown image ${id}`)
|
||||||
|
|
||||||
var image = create_image(ipath)
|
var image = create_image(ipath)
|
||||||
cache.set(id, image)
|
cache[id] = image
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
graphics.texture[cell.DOC] = `
|
graphics.texture[cell.DOC] = `
|
||||||
@@ -349,7 +343,6 @@ graphics.get_font = function get_font(path, size) {
|
|||||||
// Load font texture via renderer actor (async)
|
// Load font texture via renderer actor (async)
|
||||||
send(renderer_actor, {
|
send(renderer_actor, {
|
||||||
kind: "renderer",
|
kind: "renderer",
|
||||||
id: renderer_id,
|
|
||||||
op: "loadTexture",
|
op: "loadTexture",
|
||||||
data: font.surface
|
data: font.surface
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
@@ -372,8 +365,8 @@ Load a font from file if not cached, or retrieve from cache if already loaded.
|
|||||||
`
|
`
|
||||||
|
|
||||||
graphics.queue_sprite_mesh = function(queue) {
|
graphics.queue_sprite_mesh = function(queue) {
|
||||||
var sprites = queue.filter(x => x.type === 'sprite')
|
var sprites = queue.filter(x => x.type == 'sprite')
|
||||||
if (sprites.length === 0) return []
|
if (sprites.length == 0) return []
|
||||||
var mesh = graphics.make_sprite_mesh(sprites)
|
var mesh = graphics.make_sprite_mesh(sprites)
|
||||||
for (var i = 0; i < sprites.length; i++) {
|
for (var i = 0; i < sprites.length; i++) {
|
||||||
sprites[i].mesh = mesh
|
sprites[i].mesh = mesh
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function imtoggle(name, obj, field) {
|
|||||||
var changed = false;
|
var changed = false;
|
||||||
var old = obj[field];
|
var old = obj[field];
|
||||||
obj[field] = imgui.checkbox(name, obj[field]);
|
obj[field] = imgui.checkbox(name, obj[field]);
|
||||||
if (old !== obj[field]) return true;
|
if (old != obj[field]) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ if (render_menu) {
|
|||||||
if (debug.console)
|
if (debug.console)
|
||||||
debug.console = imgui.window("console", _ => {
|
debug.console = imgui.window("console", _ => {
|
||||||
imgui.text(log.transcript);
|
imgui.text(log.transcript);
|
||||||
replstr = imgui.textinput(undefined, replstr);
|
replstr = imgui.textinput(null, replstr);
|
||||||
imgui.button("submit", _ => {
|
imgui.button("submit", _ => {
|
||||||
eval(replstr);
|
eval(replstr);
|
||||||
replstr = "";
|
replstr = "";
|
||||||
@@ -242,14 +242,14 @@ imgui.imagebutton[cell.DOC] = `Create an ImageButton widget which displays a tex
|
|||||||
imgui.textinput[cell.DOC] = `Show a single-line text input widget.
|
imgui.textinput[cell.DOC] = `Show a single-line text input widget.
|
||||||
|
|
||||||
:param label: The label text next to the input box.
|
:param label: The label text next to the input box.
|
||||||
:param text: The current text. If undefined, the box starts empty.
|
:param text: The current text. If null, the box starts empty.
|
||||||
:return: The updated text string after user editing.
|
:return: The updated text string after user editing.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
imgui.textbox[cell.DOC] = `Show a multi-line text input widget.
|
imgui.textbox[cell.DOC] = `Show a multi-line text input widget.
|
||||||
|
|
||||||
:param label: The label text next to the input box.
|
:param label: The label text next to the input box.
|
||||||
:param text: The current multi-line text. If undefined, starts empty.
|
:param text: The current multi-line text. If null, starts empty.
|
||||||
:return: The updated text after user editing.
|
:return: The updated text after user editing.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,345 +0,0 @@
|
|||||||
var os = use('os');
|
|
||||||
var io = use('io');
|
|
||||||
var transform = use('transform');
|
|
||||||
var rasterize = use('rasterize');
|
|
||||||
var video_actor = use('sdl_video')
|
|
||||||
var input = use('input')
|
|
||||||
|
|
||||||
input.watch($_)
|
|
||||||
|
|
||||||
var geometry = use('geometry')
|
|
||||||
|
|
||||||
function worldToScreenRect({x,y,width,height}, camera, winW, winH) {
|
|
||||||
var bl = worldToScreenPoint([x,y], camera, winW, winH)
|
|
||||||
var tr = worldToScreenPoint([x+width, y+height], camera, winW, winH)
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: Math.min(bl.x, tr.x),
|
|
||||||
y: Math.min(bl.y, tr.y),
|
|
||||||
width: Math.abs(tr.x - bl.x),
|
|
||||||
height: Math.abs(tr.y - bl.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function worldToScreenPoint([wx, wy], camera, winW, winH) {
|
|
||||||
// 1) world‐window origin (bottom‐left)
|
|
||||||
const worldX0 = camera.pos[0] - camera.size[0] * camera.anchor[0];
|
|
||||||
const worldY0 = camera.pos[1] - camera.size[1] * camera.anchor[1];
|
|
||||||
|
|
||||||
// 2) normalized device coords [0..1]
|
|
||||||
const ndcX = (wx - worldX0) / camera.size[0];
|
|
||||||
const ndcY = (wy - worldY0) / camera.size[1];
|
|
||||||
|
|
||||||
// 3) map into pixel‐space via the fractional viewport
|
|
||||||
const px = camera.viewport.x * winW
|
|
||||||
+ ndcX * (camera.viewport.width * winW);
|
|
||||||
const py = camera.viewport.y * winH
|
|
||||||
+ (1 - ndcY) * (camera.viewport.height * winH);
|
|
||||||
|
|
||||||
return [ px, py ];
|
|
||||||
}
|
|
||||||
|
|
||||||
function screenToWorldPoint([px, py], camera, winW, winH) {
|
|
||||||
// 1) undo pixel→NDC within the camera’s viewport
|
|
||||||
const ndcX = (px - camera.viewport.x * winW)
|
|
||||||
/ (camera.viewport.width * winW)
|
|
||||||
const ndcY = 1 - (py - camera.viewport.y * winH)
|
|
||||||
/ (camera.viewport.height * winH)
|
|
||||||
|
|
||||||
// 2) compute the world‐window origin (bottom‐left)
|
|
||||||
const worldX0 = camera.pos[0]
|
|
||||||
- camera.size[0] * camera.anchor[0]
|
|
||||||
const worldY0 = camera.pos[1]
|
|
||||||
- camera.size[1] * camera.anchor[1]
|
|
||||||
|
|
||||||
// 3) map NDC back to world coords
|
|
||||||
return [
|
|
||||||
ndcX * camera.size[0] + worldX0,
|
|
||||||
ndcY * camera.size[1] + worldY0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
var camera = {
|
|
||||||
size: [500,500],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
|
|
||||||
pos: [250,250],//{x:0,y:0}, // where it is
|
|
||||||
fov:50,
|
|
||||||
near_z:0,
|
|
||||||
far_z:1000,
|
|
||||||
viewport: {x:0,y:0,width:1,height:1}, // viewport it appears on screen
|
|
||||||
ortho:true,
|
|
||||||
anchor:[0.5,0.5],//{x:0.5,y:0.5},
|
|
||||||
rotation:[0,0,0,1],
|
|
||||||
surface: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
var util = use('util')
|
|
||||||
log.console(util)
|
|
||||||
log.console(camera)
|
|
||||||
var cammy = util.camera_globals(camera)
|
|
||||||
|
|
||||||
var graphics
|
|
||||||
|
|
||||||
var window
|
|
||||||
var render
|
|
||||||
|
|
||||||
var gameactor
|
|
||||||
|
|
||||||
var game = args[0]
|
|
||||||
|
|
||||||
$_.start(e => {
|
|
||||||
if (gameactor) return
|
|
||||||
gameactor = e.actor
|
|
||||||
loop()
|
|
||||||
}, args[0])
|
|
||||||
|
|
||||||
send(video_actor, {
|
|
||||||
kind: "window",
|
|
||||||
op:"create",
|
|
||||||
data: {
|
|
||||||
title: "Moth Test",
|
|
||||||
width: 500,
|
|
||||||
height: 500
|
|
||||||
}
|
|
||||||
}, e => {
|
|
||||||
if (e.error) {
|
|
||||||
log.error(e.error)
|
|
||||||
os.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
window = e.id
|
|
||||||
|
|
||||||
send(video_actor,{
|
|
||||||
kind:"window",
|
|
||||||
op:"makeRenderer",
|
|
||||||
id:window
|
|
||||||
}, e => {
|
|
||||||
if (e.error) {
|
|
||||||
log.error(e.error)
|
|
||||||
os.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
render = e.id
|
|
||||||
graphics = use('graphics', video_actor, e.id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var last = os.now()
|
|
||||||
|
|
||||||
// FPS tracking
|
|
||||||
var fps_samples = []
|
|
||||||
var fps_sample_count = 60
|
|
||||||
var fps_sum = 0
|
|
||||||
|
|
||||||
var images = {}
|
|
||||||
|
|
||||||
// Convert high-level draw commands to low-level renderer commands
|
|
||||||
function translate_draw_commands(commands) {
|
|
||||||
if (!graphics) return
|
|
||||||
var renderer_commands = []
|
|
||||||
|
|
||||||
commands.forEach(function(cmd) {
|
|
||||||
if (cmd.material && cmd.material.color) {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "set",
|
|
||||||
prop: "drawColor",
|
|
||||||
value: cmd.material.color
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(cmd.cmd) {
|
|
||||||
case "draw_rect":
|
|
||||||
cmd.rect = worldToScreenRect(cmd.rect, camera,500, 500)
|
|
||||||
// Handle rectangles with optional rounding and thickness
|
|
||||||
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
|
|
||||||
// Rounded rectangle
|
|
||||||
var thickness = (cmd.opt.thickness === 0) ? 0 : (cmd.opt.thickness || 1)
|
|
||||||
var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness)
|
|
||||||
|
|
||||||
if (raster_result.type === 'rect') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: raster_result.data}
|
|
||||||
})
|
|
||||||
} else if (raster_result.type === 'rects') {
|
|
||||||
raster_result.data.forEach(function(rect) {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: rect}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) {
|
|
||||||
// Outlined rectangle
|
|
||||||
var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness)
|
|
||||||
|
|
||||||
if (raster_result.type === 'rect') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: raster_result.data}
|
|
||||||
})
|
|
||||||
} else if (raster_result.type === 'rects') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "rects",
|
|
||||||
data: {rects: raster_result.data}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: cmd.rect}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_circle":
|
|
||||||
case "draw_ellipse":
|
|
||||||
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
|
|
||||||
// Rasterize ellipse to points or rects
|
|
||||||
var radii = cmd.radii || [cmd.radius, cmd.radius]
|
|
||||||
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
|
|
||||||
|
|
||||||
if (raster_result.type === 'points') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "point",
|
|
||||||
data: {points: raster_result.data}
|
|
||||||
})
|
|
||||||
} else if (raster_result.type === 'rects') {
|
|
||||||
// Use 'rects' operation for multiple rectangles
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "rects",
|
|
||||||
data: {rects: raster_result.data}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_line":
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "line",
|
|
||||||
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera, 500, 500))}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_point":
|
|
||||||
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "point",
|
|
||||||
data: {points: [cmd.pos]}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_image":
|
|
||||||
var img = graphics.texture(cmd.image)
|
|
||||||
if (!img.gpu) break
|
|
||||||
|
|
||||||
cmd.rect.width ??= img.width
|
|
||||||
cmd.rect.height ??= img.height
|
|
||||||
cmd.rect = worldToScreenRect(cmd.rect, camera, 500, 500)
|
|
||||||
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "texture",
|
|
||||||
data: {
|
|
||||||
texture_id: img.gpu.id,
|
|
||||||
dst: cmd.rect,
|
|
||||||
src: {x:0,y:0,width:img.width,height:img.height},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_text":
|
|
||||||
if (!cmd.text) break
|
|
||||||
if (!cmd.pos) break
|
|
||||||
var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera, 500,500)
|
|
||||||
var pos = {x: rect.x, y: rect.y}
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "debugText",
|
|
||||||
data: {
|
|
||||||
pos,
|
|
||||||
text: cmd.text
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return renderer_commands
|
|
||||||
}
|
|
||||||
|
|
||||||
function loop()
|
|
||||||
{
|
|
||||||
os.frame()
|
|
||||||
var now = os.now()
|
|
||||||
var dt = now - last
|
|
||||||
last = now
|
|
||||||
|
|
||||||
// Update the game
|
|
||||||
send(gameactor, {kind:'update', dt:dt}, e => {
|
|
||||||
// Get draw commands from game
|
|
||||||
send(gameactor, {kind:'draw'}, draw_commands => {
|
|
||||||
var batch_commands = []
|
|
||||||
|
|
||||||
batch_commands.push({
|
|
||||||
op: "set",
|
|
||||||
prop: "drawColor",
|
|
||||||
value: [0.1,0.1,0.15,1]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clear the screen
|
|
||||||
batch_commands.push({
|
|
||||||
op: "clear"
|
|
||||||
})
|
|
||||||
|
|
||||||
if (draw_commands && draw_commands.length > 0) {
|
|
||||||
var renderer_commands = translate_draw_commands(draw_commands)
|
|
||||||
batch_commands = batch_commands.concat(renderer_commands)
|
|
||||||
}
|
|
||||||
|
|
||||||
batch_commands.push({
|
|
||||||
op: "present"
|
|
||||||
})
|
|
||||||
|
|
||||||
send(video_actor, {
|
|
||||||
kind: "renderer",
|
|
||||||
id: render,
|
|
||||||
op: "batch",
|
|
||||||
data: batch_commands
|
|
||||||
}, _ => {
|
|
||||||
var diff = os.now() - now
|
|
||||||
|
|
||||||
// Calculate and track FPS
|
|
||||||
var frame_time = os.now() - last
|
|
||||||
if (frame_time > 0) {
|
|
||||||
var current_fps = 1 / frame_time
|
|
||||||
|
|
||||||
// Add to samples
|
|
||||||
fps_samples.push(current_fps)
|
|
||||||
fps_sum += current_fps
|
|
||||||
|
|
||||||
// Keep only the last N samples
|
|
||||||
if (fps_samples.length > fps_sample_count) {
|
|
||||||
fps_sum -= fps_samples.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate average FPS
|
|
||||||
var avg_fps = fps_sum / fps_samples.length
|
|
||||||
}
|
|
||||||
|
|
||||||
loop()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$_.receiver(e => {
|
|
||||||
if (e.type === 'quit')
|
|
||||||
$_.stop()
|
|
||||||
|
|
||||||
if (e.type.includes('mouse')) {
|
|
||||||
if (e.pos)
|
|
||||||
e.pos = screenToWorldPoint(e.pos, camera, 500, 500)
|
|
||||||
|
|
||||||
if (e.d_pos)
|
|
||||||
e.d_pos.y *= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
send(gameactor, e)
|
|
||||||
})
|
|
||||||
@@ -1,354 +0,0 @@
|
|||||||
/**
|
|
||||||
* Moth Game Framework
|
|
||||||
* Higher-level game development framework built on top of Prosperon
|
|
||||||
*/
|
|
||||||
|
|
||||||
var os = use('os');
|
|
||||||
var io = use('io');
|
|
||||||
var transform = use('transform');
|
|
||||||
var rasterize = use('rasterize');
|
|
||||||
var video_actor = use('sdl_video')
|
|
||||||
var input = use('input')
|
|
||||||
|
|
||||||
input.watch($_)
|
|
||||||
|
|
||||||
var geometry = use('geometry')
|
|
||||||
|
|
||||||
function worldToScreenRect({x,y,width,height}, camera, winW, winH) {
|
|
||||||
var bl = worldToScreenPoint([x,y], camera, winW, winH)
|
|
||||||
var tr = worldToScreenPoint([x+width, y+height], camera, winW, winH)
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: Math.min(bl.x, tr.x),
|
|
||||||
y: Math.min(bl.y, tr.y),
|
|
||||||
width: Math.abs(tr.x - bl.x),
|
|
||||||
height: Math.abs(tr.y - bl.y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function worldToScreenPoint([wx, wy], camera, winW, winH) {
|
|
||||||
// 1) world‐window origin (bottom‐left)
|
|
||||||
const worldX0 = camera.pos[0] - camera.size[0] * camera.anchor[0];
|
|
||||||
const worldY0 = camera.pos[1] - camera.size[1] * camera.anchor[1];
|
|
||||||
|
|
||||||
// 2) normalized device coords [0..1]
|
|
||||||
const ndcX = (wx - worldX0) / camera.size[0];
|
|
||||||
const ndcY = (wy - worldY0) / camera.size[1];
|
|
||||||
|
|
||||||
// 3) map into pixel‐space via the fractional viewport
|
|
||||||
const px = camera.viewport.x * winW
|
|
||||||
+ ndcX * (camera.viewport.width * winW);
|
|
||||||
const py = camera.viewport.y * winH
|
|
||||||
+ (1 - ndcY) * (camera.viewport.height * winH);
|
|
||||||
|
|
||||||
return [ px, py ];
|
|
||||||
}
|
|
||||||
|
|
||||||
function screenToWorldPoint([px, py], camera, winW, winH) {
|
|
||||||
// 1) undo pixel→NDC within the camera’s viewport
|
|
||||||
const ndcX = (px - camera.viewport.x * winW)
|
|
||||||
/ (camera.viewport.width * winW)
|
|
||||||
const ndcY = 1 - (py - camera.viewport.y * winH)
|
|
||||||
/ (camera.viewport.height * winH)
|
|
||||||
|
|
||||||
// 2) compute the world‐window origin (bottom‐left)
|
|
||||||
const worldX0 = camera.pos[0]
|
|
||||||
- camera.size[0] * camera.anchor[0]
|
|
||||||
const worldY0 = camera.pos[1]
|
|
||||||
- camera.size[1] * camera.anchor[1]
|
|
||||||
|
|
||||||
// 3) map NDC back to world coords
|
|
||||||
return [
|
|
||||||
ndcX * camera.size[0] + worldX0,
|
|
||||||
ndcY * camera.size[1] + worldY0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
var camera = {
|
|
||||||
size: [500,500],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
|
|
||||||
pos: [250,250],//{x:0,y:0}, // where it is
|
|
||||||
fov:50,
|
|
||||||
near_z:0,
|
|
||||||
far_z:1000,
|
|
||||||
viewport: {x:0,y:0,width:1,height:1}, // viewport it appears on screen
|
|
||||||
ortho:true,
|
|
||||||
anchor:[0.5,0.5],//{x:0.5,y:0.5},
|
|
||||||
surface: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
var util = use('util')
|
|
||||||
var cammy = util.camera_globals(camera)
|
|
||||||
|
|
||||||
var graphics
|
|
||||||
|
|
||||||
var window
|
|
||||||
var render
|
|
||||||
|
|
||||||
var gameactor
|
|
||||||
|
|
||||||
var dir = args[0]
|
|
||||||
|
|
||||||
if (!io.exists(args[0] + '/main.js'))
|
|
||||||
throw Error(`No main.js found in ${args[0]}`)
|
|
||||||
|
|
||||||
log.spam('Starting game in ' + dir)
|
|
||||||
|
|
||||||
io.mount(dir)
|
|
||||||
|
|
||||||
$_.start(e => {
|
|
||||||
if (gameactor) return
|
|
||||||
gameactor = e.actor
|
|
||||||
loop()
|
|
||||||
}, args[0] + "/main.js")
|
|
||||||
|
|
||||||
send(video_actor, {
|
|
||||||
kind: "window",
|
|
||||||
op:"create",
|
|
||||||
data: {
|
|
||||||
title: "Moth Test",
|
|
||||||
width: 500,
|
|
||||||
height: 500
|
|
||||||
}
|
|
||||||
}, e => {
|
|
||||||
if (e.error) {
|
|
||||||
log.error(e.error)
|
|
||||||
os.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
window = e.id
|
|
||||||
|
|
||||||
send(video_actor,{
|
|
||||||
kind:"window",
|
|
||||||
op:"makeRenderer",
|
|
||||||
id:window
|
|
||||||
}, e => {
|
|
||||||
if (e.error) {
|
|
||||||
log.error(e.error)
|
|
||||||
os.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
render = e.id
|
|
||||||
graphics = use('graphics', video_actor, e.id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
var last = os.now()
|
|
||||||
|
|
||||||
// FPS tracking
|
|
||||||
var fps_samples = []
|
|
||||||
var fps_sample_count = 60
|
|
||||||
var fps_sum = 0
|
|
||||||
|
|
||||||
var images = {}
|
|
||||||
|
|
||||||
// Convert high-level draw commands to low-level renderer commands
|
|
||||||
function translate_draw_commands(commands) {
|
|
||||||
if (!graphics) return
|
|
||||||
var renderer_commands = []
|
|
||||||
|
|
||||||
commands.forEach(function(cmd) {
|
|
||||||
if (cmd.material && cmd.material.color) {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "set",
|
|
||||||
prop: "drawColor",
|
|
||||||
value: cmd.material.color
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(cmd.cmd) {
|
|
||||||
case "draw_rect":
|
|
||||||
cmd.rect = worldToScreenRect(cmd.rect, camera,500, 500)
|
|
||||||
// Handle rectangles with optional rounding and thickness
|
|
||||||
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
|
|
||||||
// Rounded rectangle
|
|
||||||
var thickness = (cmd.opt.thickness === 0) ? 0 : (cmd.opt.thickness || 1)
|
|
||||||
var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness)
|
|
||||||
|
|
||||||
if (raster_result.type === 'rect') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: raster_result.data}
|
|
||||||
})
|
|
||||||
} else if (raster_result.type === 'rects') {
|
|
||||||
raster_result.data.forEach(function(rect) {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: rect}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) {
|
|
||||||
// Outlined rectangle
|
|
||||||
var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness)
|
|
||||||
|
|
||||||
if (raster_result.type === 'rect') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: raster_result.data}
|
|
||||||
})
|
|
||||||
} else if (raster_result.type === 'rects') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "rects",
|
|
||||||
data: {rects: raster_result.data}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "fillRect",
|
|
||||||
data: {rect: cmd.rect}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_circle":
|
|
||||||
case "draw_ellipse":
|
|
||||||
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
|
|
||||||
// Rasterize ellipse to points or rects
|
|
||||||
var radii = cmd.radii || [cmd.radius, cmd.radius]
|
|
||||||
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
|
|
||||||
|
|
||||||
if (raster_result.type === 'points') {
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "point",
|
|
||||||
data: {points: raster_result.data}
|
|
||||||
})
|
|
||||||
} else if (raster_result.type === 'rects') {
|
|
||||||
// Use 'rects' operation for multiple rectangles
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "rects",
|
|
||||||
data: {rects: raster_result.data}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_line":
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "line",
|
|
||||||
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera, 500, 500))}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_point":
|
|
||||||
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "point",
|
|
||||||
data: {points: [cmd.pos]}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_image":
|
|
||||||
var img = graphics.texture(cmd.image)
|
|
||||||
if (!img.gpu) break
|
|
||||||
|
|
||||||
cmd.rect.width ??= img.width
|
|
||||||
cmd.rect.height ??= img.height
|
|
||||||
cmd.rect = worldToScreenRect(cmd.rect, camera, 500, 500)
|
|
||||||
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "texture",
|
|
||||||
data: {
|
|
||||||
texture_id: img.gpu.id,
|
|
||||||
dst: cmd.rect,
|
|
||||||
src: {x:0,y:0,width:img.width,height:img.height},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case "draw_text":
|
|
||||||
if (!cmd.text) break
|
|
||||||
if (!cmd.pos) break
|
|
||||||
var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera, 500,500)
|
|
||||||
var pos = {x: rect.x, y: rect.y}
|
|
||||||
renderer_commands.push({
|
|
||||||
op: "debugText",
|
|
||||||
data: {
|
|
||||||
pos,
|
|
||||||
text: cmd.text
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return renderer_commands
|
|
||||||
}
|
|
||||||
|
|
||||||
function loop()
|
|
||||||
{
|
|
||||||
os.frame()
|
|
||||||
var now = os.now()
|
|
||||||
var dt = now - last
|
|
||||||
last = now
|
|
||||||
|
|
||||||
// Update the game
|
|
||||||
send(gameactor, {kind:'update', dt:dt}, e => {
|
|
||||||
// Get draw commands from game
|
|
||||||
send(gameactor, {kind:'draw'}, draw_commands => {
|
|
||||||
var batch_commands = []
|
|
||||||
|
|
||||||
batch_commands.push({
|
|
||||||
op: "set",
|
|
||||||
prop: "drawColor",
|
|
||||||
value: [0.1,0.1,0.15,1]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clear the screen
|
|
||||||
batch_commands.push({
|
|
||||||
op: "clear"
|
|
||||||
})
|
|
||||||
|
|
||||||
if (draw_commands && draw_commands.length > 0) {
|
|
||||||
var renderer_commands = translate_draw_commands(draw_commands)
|
|
||||||
batch_commands = batch_commands.concat(renderer_commands)
|
|
||||||
}
|
|
||||||
|
|
||||||
batch_commands.push({
|
|
||||||
op: "present"
|
|
||||||
})
|
|
||||||
|
|
||||||
send(video_actor, {
|
|
||||||
kind: "renderer",
|
|
||||||
id: render,
|
|
||||||
op: "batch",
|
|
||||||
data: batch_commands
|
|
||||||
}, _ => {
|
|
||||||
var diff = os.now() - now
|
|
||||||
|
|
||||||
// Calculate and track FPS
|
|
||||||
var frame_time = os.now() - last
|
|
||||||
if (frame_time > 0) {
|
|
||||||
var current_fps = 1 / frame_time
|
|
||||||
|
|
||||||
// Add to samples
|
|
||||||
fps_samples.push(current_fps)
|
|
||||||
fps_sum += current_fps
|
|
||||||
|
|
||||||
// Keep only the last N samples
|
|
||||||
if (fps_samples.length > fps_sample_count) {
|
|
||||||
fps_sum -= fps_samples.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate average FPS
|
|
||||||
var avg_fps = fps_sum / fps_samples.length
|
|
||||||
}
|
|
||||||
|
|
||||||
loop()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$_.receiver(e => {
|
|
||||||
if (e.type === 'quit')
|
|
||||||
$_.stop()
|
|
||||||
|
|
||||||
if (e.type.includes('mouse')) {
|
|
||||||
if (e.pos)
|
|
||||||
e.pos = screenToWorldPoint(e.pos, camera, 500, 500)
|
|
||||||
|
|
||||||
if (e.d_pos)
|
|
||||||
e.d_pos.y *= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
send(gameactor, e)
|
|
||||||
})
|
|
||||||
414
prosperon/prosperon.ce
Normal file
414
prosperon/prosperon.ce
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
var os = use('os');
|
||||||
|
var io = use('io');
|
||||||
|
var transform = use('transform');
|
||||||
|
var rasterize = use('rasterize');
|
||||||
|
var time = use('time')
|
||||||
|
var tilemap = use('tilemap')
|
||||||
|
|
||||||
|
// Frame timing variables
|
||||||
|
var frame_times = []
|
||||||
|
var frame_time_index = 0
|
||||||
|
var max_frame_samples = 60
|
||||||
|
var frame_start_time = 0
|
||||||
|
var average_frame_time = 0
|
||||||
|
|
||||||
|
var game = args[0]
|
||||||
|
|
||||||
|
var video
|
||||||
|
|
||||||
|
var cnf = use('accio/config')
|
||||||
|
|
||||||
|
$_.start(e => {
|
||||||
|
if (e.type != 'greet') return
|
||||||
|
video = e.actor
|
||||||
|
graphics = use('graphics', video)
|
||||||
|
send(video, {kind:"window", op:"makeRenderer"}, e => {
|
||||||
|
$_.start(e => {
|
||||||
|
if (gameactor) return
|
||||||
|
gameactor = e.actor
|
||||||
|
$_.couple(gameactor)
|
||||||
|
start_pipeline()
|
||||||
|
}, args[0], $_)
|
||||||
|
})
|
||||||
|
}, 'prosperon/sdl_video', cnf)
|
||||||
|
|
||||||
|
var geometry = use('geometry')
|
||||||
|
|
||||||
|
function updateCameraMatrix(camera, winW, winH) {
|
||||||
|
// world→NDC
|
||||||
|
def sx = 1 / camera.size[0];
|
||||||
|
def sy = 1 / camera.size[1];
|
||||||
|
def ox = camera.pos[0] - camera.size[0] * camera.anchor[0];
|
||||||
|
def oy = camera.pos[1] - camera.size[1] * camera.anchor[1];
|
||||||
|
|
||||||
|
// NDC→pixels
|
||||||
|
def vx = camera.viewport.x * winW;
|
||||||
|
def vy = camera.viewport.y * winH;
|
||||||
|
def vw = camera.viewport.width * winW;
|
||||||
|
def vh = camera.viewport.height * winH;
|
||||||
|
|
||||||
|
// final “mat” coefficients
|
||||||
|
// [ a 0 c ]
|
||||||
|
// [ 0 e f ]
|
||||||
|
// [ 0 0 1 ]
|
||||||
|
camera.a = sx * vw;
|
||||||
|
camera.c = vx - camera.a * ox;
|
||||||
|
camera.e = -sy * vh;
|
||||||
|
camera.f = vy + vh + sy * vh * oy;
|
||||||
|
|
||||||
|
// and store the inverses so we can go back cheaply
|
||||||
|
camera.ia = 1 / camera.a;
|
||||||
|
camera.ic = -camera.c * camera.ia;
|
||||||
|
camera.ie = 1 / camera.e;
|
||||||
|
camera.if = -camera.f * camera.ie;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- forward transform ----
|
||||||
|
function worldToScreenPoint(pos, camera) {
|
||||||
|
return {
|
||||||
|
x: camera.a * pos[0] + camera.c,
|
||||||
|
y: camera.e * pos[1] + camera.f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- inverse transform ----
|
||||||
|
function screenToWorldPoint(pos, camera) {
|
||||||
|
return {
|
||||||
|
x: camera.ia * pos[0] + camera.ic,
|
||||||
|
y: camera.ie * pos[1] + camera.if
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- rectangle (two corner) ----
|
||||||
|
function worldToScreenRect(rect, camera) {
|
||||||
|
// map bottom-left and top-right
|
||||||
|
def x1 = camera.a * rect.x + camera.c;
|
||||||
|
def y1 = camera.e * rect.y + camera.f;
|
||||||
|
def x2 = camera.a * (rect.x + rect.width) + camera.c;
|
||||||
|
def y2 = camera.e * (rect.y + rect.height) + camera.f;
|
||||||
|
|
||||||
|
// pick mins and abs deltas
|
||||||
|
def x0 = x1 < x2 ? x1 : x2;
|
||||||
|
def y0 = y1 < y2 ? y1 : y2;
|
||||||
|
return {
|
||||||
|
x: x0,
|
||||||
|
y: y0,
|
||||||
|
width: x2 > x1 ? x2 - x1 : x1 - x2,
|
||||||
|
height: y2 > y1 ? y2 - y1 : y1 - y2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var camera = {
|
||||||
|
size: [640,480],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
|
||||||
|
pos: [250,250],//{x:0,y:0}, // where it is
|
||||||
|
fov:50,
|
||||||
|
near_z:0,
|
||||||
|
far_z:1000,
|
||||||
|
viewport: {x:0,y:0,width:1,height:1}, // viewport it appears on screen
|
||||||
|
ortho:true,
|
||||||
|
anchor:[0.5,0.5],//{x:0.5,y:0.5},
|
||||||
|
rotation:[0,0,0,1],
|
||||||
|
surface: null
|
||||||
|
}
|
||||||
|
|
||||||
|
var util = use('util')
|
||||||
|
var cammy = util.camera_globals(camera)
|
||||||
|
|
||||||
|
var graphics
|
||||||
|
|
||||||
|
var gameactor
|
||||||
|
|
||||||
|
var images = {}
|
||||||
|
|
||||||
|
var renderer_commands = []
|
||||||
|
|
||||||
|
// Convert high-level draw commands to low-level renderer commands
|
||||||
|
function translate_draw_commands(commands) {
|
||||||
|
if (!graphics) return
|
||||||
|
|
||||||
|
updateCameraMatrix(camera,500,500)
|
||||||
|
|
||||||
|
renderer_commands.length = 0
|
||||||
|
|
||||||
|
commands.forEach(function(cmd) {
|
||||||
|
if (cmd.material && cmd.material.color) {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "set",
|
||||||
|
prop: "drawColor",
|
||||||
|
value: cmd.material.color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(cmd.cmd) {
|
||||||
|
case "draw_rect":
|
||||||
|
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||||
|
// Handle rectangles with optional rounding and thickness
|
||||||
|
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
|
||||||
|
// Rounded rectangle
|
||||||
|
var thickness = (cmd.opt.thickness == 0) ? 0 : (cmd.opt.thickness || 1)
|
||||||
|
var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness)
|
||||||
|
|
||||||
|
if (raster_result.type == 'rect') {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "fillRect",
|
||||||
|
data: {rect: raster_result.data}
|
||||||
|
})
|
||||||
|
} else if (raster_result.type == 'rects') {
|
||||||
|
raster_result.data.forEach(function(rect) {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "fillRect",
|
||||||
|
data: {rect: rect}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) {
|
||||||
|
// Outlined rectangle
|
||||||
|
var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness)
|
||||||
|
|
||||||
|
if (raster_result.type == 'rect') {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "fillRect",
|
||||||
|
data: {rect: raster_result.data}
|
||||||
|
})
|
||||||
|
} else if (raster_result.type == 'rects') {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "rects",
|
||||||
|
data: {rects: raster_result.data}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "fillRect",
|
||||||
|
data: {rect: cmd.rect}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_circle":
|
||||||
|
case "draw_ellipse":
|
||||||
|
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||||
|
// Rasterize ellipse to points or rects
|
||||||
|
var radii = cmd.radii || [cmd.radius, cmd.radius]
|
||||||
|
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
|
||||||
|
|
||||||
|
if (raster_result.type == 'points') {
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "point",
|
||||||
|
data: {points: raster_result.data}
|
||||||
|
})
|
||||||
|
} else if (raster_result.type == 'rects') {
|
||||||
|
// Use 'rects' operation for multiple rectangles
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "rects",
|
||||||
|
data: {rects: raster_result.data}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_line":
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "line",
|
||||||
|
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera))}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_point":
|
||||||
|
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "point",
|
||||||
|
data: {points: [cmd.pos]}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_image":
|
||||||
|
var img = graphics.texture(cmd.image)
|
||||||
|
var gpu = img.gpu
|
||||||
|
if (!gpu) break
|
||||||
|
|
||||||
|
cmd.rect.width ??= img.width
|
||||||
|
cmd.rect.height ??= img.height
|
||||||
|
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||||
|
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "texture",
|
||||||
|
data: {
|
||||||
|
texture_id: gpu.id,
|
||||||
|
dst: cmd.rect,
|
||||||
|
src: img.rect
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_text":
|
||||||
|
if (!cmd.text) break
|
||||||
|
if (!cmd.pos) break
|
||||||
|
var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera, 500,500)
|
||||||
|
var pos = {x: rect.x, y: rect.y}
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "debugText",
|
||||||
|
data: {
|
||||||
|
pos,
|
||||||
|
text: cmd.text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case "tilemap":
|
||||||
|
var texid
|
||||||
|
tilemap.for(cmd.tilemap, (tile,{x,y}) => {
|
||||||
|
if (!texid) texid = graphics.texture(tile)
|
||||||
|
return graphics.texture(tile)
|
||||||
|
})
|
||||||
|
var geom = geometry.tilemap_to_data(cmd.tilemap)
|
||||||
|
if (!texid) break
|
||||||
|
if (texid.gpu)
|
||||||
|
geom.texture_id = texid.gpu.id
|
||||||
|
|
||||||
|
renderer_commands.push({
|
||||||
|
op: "geometry_raw",
|
||||||
|
data: geom
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return renderer_commands
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseq = use('parseq', $_.delay)
|
||||||
|
|
||||||
|
// Wrap `send(actor,msg,cb)` into a parseq “requestor”
|
||||||
|
// • on success: cb(data) → value=data, reason=null
|
||||||
|
// • on failure: cb(null,err)
|
||||||
|
function rpc_req(actor, msg) {
|
||||||
|
return (cb, _) => {
|
||||||
|
send(actor, msg, data => {
|
||||||
|
if (data.error)
|
||||||
|
cb(null, data)
|
||||||
|
else
|
||||||
|
cb(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var game_rec = parseq.sequence([
|
||||||
|
rpc_req(gameactor, {kind:'update', dt:1/60}),
|
||||||
|
rpc_req(gameactor, {kind:'draw'})
|
||||||
|
])
|
||||||
|
|
||||||
|
var pending_draw = null
|
||||||
|
var pending_next = null
|
||||||
|
var last_time = time.number()
|
||||||
|
var frames = []
|
||||||
|
var frame_avg = 0
|
||||||
|
|
||||||
|
var input = use('input')
|
||||||
|
|
||||||
|
var input_state = {
|
||||||
|
poll: 1/60
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) input runs completely independently
|
||||||
|
function poll_input() {
|
||||||
|
send(video, {kind:'input', op:'get'}, evs => {
|
||||||
|
for (var ev of evs) {
|
||||||
|
if (ev.type == 'quit')
|
||||||
|
$_.stop()
|
||||||
|
|
||||||
|
if (ev.type.includes('mouse')) {
|
||||||
|
if (ev.pos)
|
||||||
|
ev.pos = screenToWorldPoint(ev.pos, camera, 500,500)
|
||||||
|
|
||||||
|
if (ev.d_pos)
|
||||||
|
ev.d_pos.y *= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.type.includes('key')) {
|
||||||
|
if (ev.key)
|
||||||
|
ev.key = input.keyname(ev.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send(gameactor, evs)
|
||||||
|
})
|
||||||
|
$_.delay(poll_input, input_state.poll)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) helper to build & send a batch, then call done()
|
||||||
|
function create_batch(draw_cmds, done) {
|
||||||
|
def batch = [
|
||||||
|
{op:'set', prop:'drawColor', value:[0.1,0.1,0.15,1]},
|
||||||
|
{op:'clear'}
|
||||||
|
]
|
||||||
|
if (draw_cmds && draw_cmds.length)
|
||||||
|
batch.push(...translate_draw_commands(draw_cmds))
|
||||||
|
|
||||||
|
batch.push(
|
||||||
|
{op:'set', prop:'drawColor', value:[1,1,1,1]},
|
||||||
|
{op:'debugText', data:{pos:{x:10,y:10}, text:`Fps: ${(1/frame_avg).toFixed(2)}`}},
|
||||||
|
{op:'present'}
|
||||||
|
)
|
||||||
|
|
||||||
|
send(video, {kind:'renderer', op:'batch', data:batch}, () => {
|
||||||
|
def now = time.number()
|
||||||
|
def dt = now - last_time
|
||||||
|
last_time = now
|
||||||
|
|
||||||
|
frames.push(dt)
|
||||||
|
if (frames.length > 60) frames.shift()
|
||||||
|
let sum = 0
|
||||||
|
for (let f of frames) sum += f
|
||||||
|
frame_avg = sum / frames.length
|
||||||
|
|
||||||
|
done(dt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) kick off the very first update→draw
|
||||||
|
function start_pipeline() {
|
||||||
|
poll_input()
|
||||||
|
send(gameactor, {kind:'update', dt:1/60}, () => {
|
||||||
|
send(gameactor, {kind:'draw'}, cmds => {
|
||||||
|
pending_draw = cmds
|
||||||
|
render_step()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_step() {
|
||||||
|
// a) fire off the next update→draw immediately
|
||||||
|
def dt = time.number() - last_time
|
||||||
|
send(gameactor, {kind:'update', dt:1/60}, () =>
|
||||||
|
send(gameactor, {kind:'draw'}, cmds => pending_next = cmds)
|
||||||
|
)
|
||||||
|
|
||||||
|
// c) render the current frame
|
||||||
|
create_batch(pending_draw, ttr => { // time to render
|
||||||
|
// only swap in when there's a new set of commands
|
||||||
|
if (pending_next) {
|
||||||
|
pending_draw = pending_next
|
||||||
|
pending_next = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// d) schedule the next render step
|
||||||
|
def render_dur = time.number() - last_time
|
||||||
|
def wait = Math.max(0, 1/60 - ttr)
|
||||||
|
$_.delay(render_step, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$_.receiver(e => {
|
||||||
|
switch(e.op) {
|
||||||
|
case 'resolution':
|
||||||
|
log.console(json.encode(e))
|
||||||
|
send(video, {
|
||||||
|
kind:'renderer',
|
||||||
|
op:'set',
|
||||||
|
prop:'logicalPresentation',
|
||||||
|
value: {...e}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -35,7 +35,7 @@ rasterize.ellipse = function ellipse(pos, radii, opt) {
|
|||||||
ry_i = ry - thickness
|
ry_i = ry - thickness
|
||||||
var hole = (rx_i > 0 && ry_i > 0)
|
var hole = (rx_i > 0 && ry_i > 0)
|
||||||
|
|
||||||
if (!hole && thickness === 1) {
|
if (!hole && thickness == 1) {
|
||||||
var points = []
|
var points = []
|
||||||
var rx_sq = rx * rx, ry_sq = ry * ry
|
var rx_sq = rx * rx, ry_sq = ry * ry
|
||||||
var two_rx_sq = rx_sq << 1, two_ry_sq = ry_sq << 1
|
var two_rx_sq = rx_sq << 1, two_ry_sq = ry_sq << 1
|
||||||
@@ -82,9 +82,9 @@ rasterize.ellipse = function ellipse(pos, radii, opt) {
|
|||||||
if (hole && Math.abs(dx) <= x_in) { run_start = null; continue }
|
if (hole && Math.abs(dx) <= x_in) { run_start = null; continue }
|
||||||
if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue }
|
if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue }
|
||||||
|
|
||||||
if (run_start === null) run_start = cx + dx
|
if (run_start == null) run_start = cx + dx
|
||||||
|
|
||||||
var last = (dx === x_out)
|
var last = (dx == x_out)
|
||||||
var next_in_ring =
|
var next_in_ring =
|
||||||
!last &&
|
!last &&
|
||||||
!(hole && Math.abs(dx+1) <= x_in) &&
|
!(hole && Math.abs(dx+1) <= x_in) &&
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
var io = use('io')
|
||||||
|
|
||||||
Object.defineProperty(Function.prototype, "hashify", {
|
Object.defineProperty(Function.prototype, "hashify", {
|
||||||
value: function () {
|
value: function () {
|
||||||
var hash = new Map()
|
var hash = {}
|
||||||
var fn = this
|
var fn = this
|
||||||
function hashified(...args) {
|
function hashified(...args) {
|
||||||
var key = args[0]
|
var key = args[0]
|
||||||
if (!hash.has(key)) hash.set(key, fn(...args))
|
if (hash[key] == null) hash[key] = fn(...args)
|
||||||
return hash.get(key)
|
return hash[key]
|
||||||
}
|
}
|
||||||
return hashified
|
return hashified
|
||||||
},
|
},
|
||||||
@@ -39,12 +41,12 @@ function isRecognizedExtension(ext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function find_in_path(filename, exts = []) {
|
function find_in_path(filename, exts = []) {
|
||||||
if (typeof filename !== 'string') return undefined
|
if (typeof filename != 'string') return null
|
||||||
|
|
||||||
if (filename.includes('.')) {
|
if (filename.includes('.')) {
|
||||||
var candidate = filename // possibly need "/" ?
|
var candidate = filename // possibly need "/" ?
|
||||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||||
return undefined
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only check extensions if exts is provided and not empty
|
// Only check extensions if exts is provided and not empty
|
||||||
@@ -58,7 +60,7 @@ function find_in_path(filename, exts = []) {
|
|||||||
var candidate = filename
|
var candidate = filename
|
||||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||||
}
|
}
|
||||||
return undefined
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a canonical path (the real directory plus the path)
|
// Return a canonical path (the real directory plus the path)
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ function add_timer(obj, fn, seconds) {
|
|||||||
var stop = function () {
|
var stop = function () {
|
||||||
if (!timer) return
|
if (!timer) return
|
||||||
timers.delete(stop)
|
timers.delete(stop)
|
||||||
timer.fn = undefined
|
timer.fn = null
|
||||||
timer = undefined
|
timer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function execute() {
|
function execute() {
|
||||||
@@ -45,7 +45,7 @@ globalThis.Register = {
|
|||||||
var fns = []
|
var fns = []
|
||||||
|
|
||||||
n.register = function (fn, oname) {
|
n.register = function (fn, oname) {
|
||||||
if (typeof fn !== 'function') return
|
if (typeof fn != 'function') return
|
||||||
|
|
||||||
var dofn = function (...args) {
|
var dofn = function (...args) {
|
||||||
fn(...args)
|
fn(...args)
|
||||||
@@ -59,7 +59,7 @@ globalThis.Register = {
|
|||||||
|
|
||||||
while (left <= right) {
|
while (left <= right) {
|
||||||
var mid = Math.floor((left + right) / 2)
|
var mid = Math.floor((left + right) / 2)
|
||||||
if (fns[mid] === dofn.layer) {
|
if (fns[mid] == dofn.layer) {
|
||||||
left = mid
|
left = mid
|
||||||
break
|
break
|
||||||
} else if (fns[mid].layer < dofn.layer) left = mid + 1
|
} else if (fns[mid].layer < dofn.layer) left = mid + 1
|
||||||
@@ -93,7 +93,7 @@ globalThis.Register = {
|
|||||||
Register.pull_registers = function pull_registers(obj) {
|
Register.pull_registers = function pull_registers(obj) {
|
||||||
var reggies = []
|
var reggies = []
|
||||||
for (var reg in Register.registries) {
|
for (var reg in Register.registries) {
|
||||||
if (typeof obj[reg] === "function")
|
if (typeof obj[reg] == "function")
|
||||||
reggies.push(reg)
|
reggies.push(reg)
|
||||||
}
|
}
|
||||||
return reggies
|
return reggies
|
||||||
@@ -115,7 +115,7 @@ Register.check_registers = function check_registers(obj) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (var reg in Register.registries) {
|
for (var reg in Register.registries) {
|
||||||
if (typeof obj[reg] === "function")
|
if (typeof obj[reg] == "function")
|
||||||
Register.register_obj(obj,reg)
|
Register.register_obj(obj,reg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,9 +203,9 @@ actor.spawn = function spawn(script, config, actor_context) {
|
|||||||
prog.prog_fn.call(underling, actor_dollar)
|
prog.prog_fn.call(underling, actor_dollar)
|
||||||
} catch(e) { throw e; }
|
} catch(e) { throw e; }
|
||||||
|
|
||||||
if (underling[DEAD]) return undefined;
|
if (underling[DEAD]) return null;
|
||||||
|
|
||||||
if (typeof config === 'object') Object.assign(underling, config)
|
if (typeof config == 'object') Object.assign(underling, config)
|
||||||
|
|
||||||
if (!underling[REGGIES])
|
if (!underling[REGGIES])
|
||||||
underling.__proto__[REGGIES] = Register.pull_registers(underling)
|
underling.__proto__[REGGIES] = Register.pull_registers(underling)
|
||||||
@@ -238,8 +238,8 @@ actor.kill = function kill() {
|
|||||||
this[OVERLING][UNDERLINGS].delete(this)
|
this[OVERLING][UNDERLINGS].delete(this)
|
||||||
delete this[UNDERLINGS]
|
delete this[UNDERLINGS]
|
||||||
|
|
||||||
if (typeof this.garbage === "function") this.garbage()
|
if (typeof this.garbage == "function") this.garbage()
|
||||||
if (typeof this.then === "function") this.then()
|
if (typeof this.then == "function") this.then()
|
||||||
|
|
||||||
act.tag_clear_guid(this)
|
act.tag_clear_guid(this)
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ function eachobj(obj, fn) {
|
|||||||
var val = fn(obj)
|
var val = fn(obj)
|
||||||
if (val) return val
|
if (val) return val
|
||||||
for (var o in obj.objects) {
|
for (var o in obj.objects) {
|
||||||
if (obj.objects[o] === obj) log.error(`Object ${obj.toString()} is referenced by itself.`)
|
if (obj.objects[o] == obj) log.error(`Object ${obj.toString()} is referenced by itself.`)
|
||||||
val = eachobj(obj.objects[o], fn)
|
val = eachobj(obj.objects[o], fn)
|
||||||
if (val) return val
|
if (val) return val
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ ex.all_objects = function (fn, startobj = world) {
|
|||||||
ex.all_objects[cell.DOC] = `
|
ex.all_objects[cell.DOC] = `
|
||||||
:param fn: A callback function that receives each object. If it returns a truthy value, iteration stops and that value is returned.
|
:param fn: A callback function that receives each object. If it returns a truthy value, iteration stops and that value is returned.
|
||||||
:param startobj: The root object at which iteration begins, default is the global "world".
|
:param startobj: The root object at which iteration begins, default is the global "world".
|
||||||
:return: The first truthy value returned by fn, or undefined if none.
|
:return: The first truthy value returned by fn, or null if none.
|
||||||
Iterate over each object (and its sub-objects) in the hierarchy, calling fn for each one.
|
Iterate over each object (and its sub-objects) in the hierarchy, calling fn for each one.
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -379,10 +379,10 @@ var script_fn = function script_fn(path, args) {
|
|||||||
// Create a context object with args
|
// Create a context object with args
|
||||||
var context = Object.create(parsed.module_ret)
|
var context = Object.create(parsed.module_ret)
|
||||||
context.__args__ = args || []
|
context.__args__ = args || []
|
||||||
var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = undefined; var arg = this.__args__; ${parsed.module}})`
|
var mod_script = `(function setup_${module_name}_module(){ var self = this; var $ = this; var exports = {}; var module = {exports: exports}; var define = null; var arg = this.__args__; ${parsed.module}})`
|
||||||
var module_fn = js.eval(file, mod_script)
|
var module_fn = js.eval(file, mod_script)
|
||||||
parsed.module_ret = module_fn.call(context)
|
parsed.module_ret = module_fn.call(context)
|
||||||
if (parsed.module_ret === undefined || parsed.module_ret === null)
|
if (parsed.module_ret == null || parsed.module_ret == null)
|
||||||
throw new Error(`Module ${module_name} must return a value`)
|
throw new Error(`Module ${module_name} must return a value`)
|
||||||
parsed.module_fn = module_fn
|
parsed.module_fn = module_fn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,14 +138,14 @@ function get_pipeline_ubo_slot(pipeline, name) {
|
|||||||
if (ubo.name.endsWith(name))
|
if (ubo.name.endsWith(name))
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_pipeline_ubo_slot[cell.DOC] = `Return the index of a uniform buffer block within the pipeline's vertex reflection data by name suffix.
|
get_pipeline_ubo_slot[cell.DOC] = `Return the index of a uniform buffer block within the pipeline's vertex reflection data by name suffix.
|
||||||
|
|
||||||
:param pipeline: The pipeline whose vertex reflection is inspected.
|
:param pipeline: The pipeline whose vertex reflection is inspected.
|
||||||
:param name: A string suffix to match against the uniform buffer block name.
|
:param name: A string suffix to match against the uniform buffer block name.
|
||||||
:return: The integer index of the matching UBO, or undefined if not found.
|
:return: The integer index of the matching UBO, or null if not found.
|
||||||
`
|
`
|
||||||
|
|
||||||
function transpose4x4(val) {
|
function transpose4x4(val) {
|
||||||
@@ -181,7 +181,7 @@ function ubo_obj_to_array(pipeline, name, obj) {
|
|||||||
var val = obj[mem.name];
|
var val = obj[mem.name];
|
||||||
if (!val) throw new Error (`Could not find ${mem.name} on supplied object`);
|
if (!val) throw new Error (`Could not find ${mem.name} on supplied object`);
|
||||||
|
|
||||||
if (mem.name === 'model')
|
if (mem.name == 'model')
|
||||||
val = transpose4x4(val.array());
|
val = transpose4x4(val.array());
|
||||||
|
|
||||||
for (var i = 0; i < val.length; i++)
|
for (var i = 0; i < val.length; i++)
|
||||||
@@ -226,9 +226,9 @@ var shader_times = {};
|
|||||||
function make_pipeline(pipeline) {
|
function make_pipeline(pipeline) {
|
||||||
if (pipeline.hasOwnProperty("gpu")) return; // this pipeline has already been made
|
if (pipeline.hasOwnProperty("gpu")) return; // this pipeline has already been made
|
||||||
|
|
||||||
if (typeof pipeline.vertex === 'string')
|
if (typeof pipeline.vertex == 'string')
|
||||||
pipeline.vertex = make_shader(pipeline.vertex);
|
pipeline.vertex = make_shader(pipeline.vertex);
|
||||||
if (typeof pipeline.fragment === 'string')
|
if (typeof pipeline.fragment == 'string')
|
||||||
pipeline.fragment = make_shader(pipeline.fragment)
|
pipeline.fragment = make_shader(pipeline.fragment)
|
||||||
|
|
||||||
// 1) Reflection data for vertex shader
|
// 1) Reflection data for vertex shader
|
||||||
@@ -309,7 +309,7 @@ function make_shader(sh_file) {
|
|||||||
num_textures: 0,
|
num_textures: 0,
|
||||||
num_storage_buffers: refl.separate_storage_buffers ? refl.separate_storage_buffers.length : 0,
|
num_storage_buffers: refl.separate_storage_buffers ? refl.separate_storage_buffers.length : 0,
|
||||||
num_uniform_buffers: refl.ubos ? refl.ubos.length : 0,
|
num_uniform_buffers: refl.ubos ? refl.ubos.length : 0,
|
||||||
entrypoint: shader_type === "msl" ? "main0" : "main"
|
entrypoint: shader_type == "msl" ? "main0" : "main"
|
||||||
}
|
}
|
||||||
|
|
||||||
shader.gpu = context.make_shader(shader)
|
shader.gpu = context.make_shader(shader)
|
||||||
@@ -349,7 +349,7 @@ var std_sampler = {
|
|||||||
function upload_model(model) {
|
function upload_model(model) {
|
||||||
var bufs = [];
|
var bufs = [];
|
||||||
for (var i in model) {
|
for (var i in model) {
|
||||||
if (typeof model[i] !== 'object') continue;
|
if (typeof model[i] != 'object') continue;
|
||||||
bufs.push(model[i]);
|
bufs.push(model[i]);
|
||||||
}
|
}
|
||||||
context.upload(this, bufs);
|
context.upload(this, bufs);
|
||||||
@@ -405,7 +405,7 @@ bind_mat[cell.DOC] = `Bind the material images and samplers needed by the pipeli
|
|||||||
`
|
`
|
||||||
|
|
||||||
function group_sprites_by_texture(sprites, mesh) {
|
function group_sprites_by_texture(sprites, mesh) {
|
||||||
if (sprites.length === 0) return;
|
if (sprites.length == 0) return;
|
||||||
for (var i = 0; i < sprites.length; i++) {
|
for (var i = 0; i < sprites.length; i++) {
|
||||||
sprites[i].mesh = mesh;
|
sprites[i].mesh = mesh;
|
||||||
sprites[i].first_index = i*6;
|
sprites[i].first_index = i*6;
|
||||||
@@ -418,7 +418,7 @@ function group_sprites_by_texture(sprites, mesh) {
|
|||||||
var group = {image:sprites[0].image, first_index:0};
|
var group = {image:sprites[0].image, first_index:0};
|
||||||
var count = 1;
|
var count = 1;
|
||||||
for (var i = 1; i < sprites.length; i++) {
|
for (var i = 1; i < sprites.length; i++) {
|
||||||
if (sprites[i].image === group.image) {
|
if (sprites[i].image == group.image) {
|
||||||
count++;
|
count++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -495,7 +495,7 @@ function render_camera(cmds, camera) {
|
|||||||
|
|
||||||
buffers = buffers.concat(graphics.queue_sprite_mesh(hud_queue));
|
buffers = buffers.concat(graphics.queue_sprite_mesh(hud_queue));
|
||||||
for (var q of hud_queue)
|
for (var q of hud_queue)
|
||||||
if (q.type === 'geometry') buffers = buffers.concat([q.mesh.pos, q.mesh.color, q.mesh.uv, q.mesh.indices]);
|
if (q.type == 'geometry') buffers = buffers.concat([q.mesh.pos, q.mesh.color, q.mesh.uv, q.mesh.indices]);
|
||||||
|
|
||||||
full_upload(buffers)
|
full_upload(buffers)
|
||||||
|
|
||||||
@@ -505,11 +505,11 @@ function render_camera(cmds, camera) {
|
|||||||
bind_pipeline(pass,pipeline);
|
bind_pipeline(pass,pipeline);
|
||||||
|
|
||||||
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
||||||
if (typeof camslot !== 'undefined')
|
if (camslot != null)
|
||||||
cmds.camera(camera, camslot);
|
cmds.camera(camera, camslot);
|
||||||
|
|
||||||
modelslot = get_pipeline_ubo_slot(pipeline, "model");
|
modelslot = get_pipeline_ubo_slot(pipeline, "model");
|
||||||
if (typeof modelslot !== 'undefined') {
|
if (modelslot != null) {
|
||||||
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo);
|
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo);
|
||||||
cmds.push_vertex_uniform_data(modelslot, ubo);
|
cmds.push_vertex_uniform_data(modelslot, ubo);
|
||||||
}
|
}
|
||||||
@@ -537,7 +537,7 @@ function render_camera(cmds, camera) {
|
|||||||
|
|
||||||
cmds.push_debug_group("hud")
|
cmds.push_debug_group("hud")
|
||||||
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
||||||
if (typeof camslot !== 'undefined')
|
if (camslot != null)
|
||||||
cmds.hud(camera.size, camslot);
|
cmds.hud(camera.size, camslot);
|
||||||
|
|
||||||
for (var group of hud_queue) {
|
for (var group of hud_queue) {
|
||||||
@@ -651,7 +651,7 @@ var stencil_invert = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function mask(image, pos, scale, rotation = 0, ref = 1) {
|
function mask(image, pos, scale, rotation = 0, ref = 1) {
|
||||||
if (typeof image === 'string')
|
if (typeof image == 'string')
|
||||||
image = graphics.texture(image);
|
image = graphics.texture(image);
|
||||||
|
|
||||||
var tex = image.texture;
|
var tex = image.texture;
|
||||||
@@ -661,7 +661,7 @@ function mask(image, pos, scale, rotation = 0, ref = 1) {
|
|||||||
var pipe = stencil_writer(ref);
|
var pipe = stencil_writer(ref);
|
||||||
render.use_shader('sprite.cg', pipe);
|
render.use_shader('sprite.cg', pipe);
|
||||||
var t = new transform;
|
var t = new transform;
|
||||||
t.trs(pos, undefined, scale);
|
t.trs(pos, null, scale);
|
||||||
set_model(t);
|
set_model(t);
|
||||||
render.use_mat({
|
render.use_mat({
|
||||||
diffuse:image.texture,
|
diffuse:image.texture,
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
var video = use('sdl_video');
|
||||||
|
|
||||||
|
log.console("STATED VIDE")
|
||||||
|
|
||||||
// SDL Video Actor
|
// SDL Video Actor
|
||||||
// This actor runs on the main thread and handles all SDL video operations
|
// This actor runs on the main thread and handles all SDL video operations
|
||||||
log.console("TO HERE")
|
var surface = use('surface');
|
||||||
var surface = use('surface')
|
var input = use('input')
|
||||||
|
|
||||||
|
var ren
|
||||||
|
var win
|
||||||
|
|
||||||
// Default window configuration - documents all available window options
|
|
||||||
var default_window = {
|
var default_window = {
|
||||||
// Basic properties
|
// Basic properties
|
||||||
title: "Prosperon Window",
|
title: "Prosperon Window",
|
||||||
@@ -11,8 +17,8 @@ var default_window = {
|
|||||||
height: 480,
|
height: 480,
|
||||||
|
|
||||||
// Position - can be numbers or "centered"
|
// Position - can be numbers or "centered"
|
||||||
x: undefined, // SDL_WINDOWPOS_UNDEFINED by default
|
x: null, // SDL_WINDOWPOS_null by default
|
||||||
y: undefined, // SDL_WINDOWPOS_UNDEFINED by default
|
y: null, // SDL_WINDOWPOS_null by default
|
||||||
|
|
||||||
// Window behavior flags
|
// Window behavior flags
|
||||||
resizable: true,
|
resizable: true,
|
||||||
@@ -46,7 +52,7 @@ var default_window = {
|
|||||||
metal: false, // Force Metal context (macOS)
|
metal: false, // Force Metal context (macOS)
|
||||||
|
|
||||||
// Advanced properties
|
// Advanced properties
|
||||||
parent: undefined, // Parent window for tooltips/popups/modal
|
parent: null, // Parent window for tooltips/popups/modal
|
||||||
modal: false, // Modal to parent window (requires parent)
|
modal: false, // Modal to parent window (requires parent)
|
||||||
externalGraphicsContext: false, // Use external graphics context
|
externalGraphicsContext: false, // Use external graphics context
|
||||||
|
|
||||||
@@ -54,10 +60,11 @@ var default_window = {
|
|||||||
textInput: true, // Enable text input on creation
|
textInput: true, // Enable text input on creation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var config = Object.assign({}, default_window, arg[0] || {});
|
||||||
|
win = new video.window(config);
|
||||||
|
|
||||||
// Resource tracking
|
// Resource tracking
|
||||||
var resources = {
|
var resources = {
|
||||||
window: {},
|
|
||||||
renderer: {},
|
|
||||||
texture: {},
|
texture: {},
|
||||||
surface: {},
|
surface: {},
|
||||||
cursor: {}
|
cursor: {}
|
||||||
@@ -103,6 +110,9 @@ $_.receiver(function(msg) {
|
|||||||
case 'keyboard':
|
case 'keyboard':
|
||||||
response = handle_keyboard(msg);
|
response = handle_keyboard(msg);
|
||||||
break;
|
break;
|
||||||
|
case 'input':
|
||||||
|
response = input.get_events();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
response = {error: "Unknown kind: " + msg.kind};
|
response = {error: "Unknown kind: " + msg.kind};
|
||||||
}
|
}
|
||||||
@@ -116,26 +126,10 @@ $_.receiver(function(msg) {
|
|||||||
|
|
||||||
// Window operations
|
// Window operations
|
||||||
function handle_window(msg) {
|
function handle_window(msg) {
|
||||||
// Special case: create doesn't need an existing window
|
|
||||||
if (msg.op === 'create') {
|
|
||||||
var config = Object.assign({}, default_window, msg.data || {});
|
|
||||||
var id = allocate_id();
|
|
||||||
var window = new prosperon.endowments.window(config);
|
|
||||||
resources.window[id] = window;
|
|
||||||
return {id: id, data: {size: window.size}};
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other operations require a valid window ID
|
|
||||||
if (!msg.id || !resources.window[msg.id]) {
|
|
||||||
return {error: "Invalid window id: " + msg.id};
|
|
||||||
}
|
|
||||||
|
|
||||||
var win = resources.window[msg.id];
|
|
||||||
|
|
||||||
switch (msg.op) {
|
switch (msg.op) {
|
||||||
case 'destroy':
|
case 'destroy':
|
||||||
win.destroy();
|
win.destroy();
|
||||||
delete resources.window[msg.id];
|
win = null
|
||||||
return {success: true};
|
return {success: true};
|
||||||
|
|
||||||
case 'show':
|
case 'show':
|
||||||
@@ -151,7 +145,7 @@ function handle_window(msg) {
|
|||||||
if (!prop) return {error: "Missing property name"};
|
if (!prop) return {error: "Missing property name"};
|
||||||
|
|
||||||
// Handle special cases
|
// Handle special cases
|
||||||
if (prop === 'surface') {
|
if (prop == 'surface') {
|
||||||
var surf = win.surface;
|
var surf = win.surface;
|
||||||
if (!surf) return {data: null};
|
if (!surf) return {data: null};
|
||||||
var surf_id = allocate_id();
|
var surf_id = allocate_id();
|
||||||
@@ -163,12 +157,12 @@ function handle_window(msg) {
|
|||||||
|
|
||||||
case 'set':
|
case 'set':
|
||||||
var prop = msg.data ? msg.data.property : null;
|
var prop = msg.data ? msg.data.property : null;
|
||||||
var value = msg.data ? msg.data.value : undefined;
|
var value = msg.data ? msg.data.value : null;
|
||||||
if (!prop) return {error: "Missing property name"};
|
if (!prop) return {error: "Missing property name"};
|
||||||
|
|
||||||
// Validate property is settable
|
// Validate property is settable
|
||||||
var readonly = ['id', 'pixelDensity', 'displayScale', 'sizeInPixels', 'flags', 'surface'];
|
var readonly = ['id', 'pixelDensity', 'displayScale', 'sizeInPixels', 'flags', 'surface'];
|
||||||
if (readonly.indexOf(prop) !== -1) {
|
if (readonly.indexOf(prop) != -1) {
|
||||||
return {error: "Property '" + prop + "' is read-only"};
|
return {error: "Property '" + prop + "' is read-only"};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,294 +206,269 @@ function handle_window(msg) {
|
|||||||
return {success: true};
|
return {success: true};
|
||||||
|
|
||||||
case 'makeRenderer':
|
case 'makeRenderer':
|
||||||
var renderer = win.make_renderer();
|
log.console("MAKE RENDERER")
|
||||||
var renderer_id = allocate_id();
|
if (ren)
|
||||||
resources.renderer[renderer_id] = renderer;
|
return {reason: "Already made a renderer"}
|
||||||
return {id: renderer_id};
|
ren = win.make_renderer()
|
||||||
|
return {success:true};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {error: "Unknown window operation: " + msg.op};
|
return {error: "Unknown window operation: " + msg.op};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renderer operation functions
|
||||||
|
var renderfuncs = {
|
||||||
|
destroy: function(msg) {
|
||||||
|
ren = null
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: function(msg) {
|
||||||
|
ren.clear();
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
present: function(msg) {
|
||||||
|
ren.present();
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
flush: function(msg) {
|
||||||
|
ren.flush();
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(msg) {
|
||||||
|
var prop = msg.data ? msg.data.property : null;
|
||||||
|
if (!prop) return {error: "Missing property name"};
|
||||||
|
|
||||||
|
// Handle special getters that might return objects
|
||||||
|
if (prop == 'drawColor') {
|
||||||
|
var color = ren[prop];
|
||||||
|
if (color && typeof color == 'object') {
|
||||||
|
// Convert color object to array format [r,g,b,a]
|
||||||
|
return {data: [color.r || 0, color.g || 0, color.b || 0, color.a || 255]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {data: ren[prop]};
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function(msg) {
|
||||||
|
var prop = msg.prop
|
||||||
|
var value = msg.value
|
||||||
|
if (!prop) return {error: "Missing property name"};
|
||||||
|
|
||||||
|
if (!value) return {error: "No value to set"}
|
||||||
|
|
||||||
|
// Validate property is settable
|
||||||
|
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
|
||||||
|
if (readonly.indexOf(prop) != -1) {
|
||||||
|
return {error: "Property '" + prop + "' is read-only"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for render target
|
||||||
|
if (prop == 'target' && value != null && value != null) {
|
||||||
|
var tex = resources.texture[value];
|
||||||
|
if (!tex) return {error: "Invalid texture id"};
|
||||||
|
value = tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
ren[prop] = value;
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
line: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.points) return {error: "Missing points array"};
|
||||||
|
ren.line(msg.data.points);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
point: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.points) return {error: "Missing points"};
|
||||||
|
ren.point(msg.data.points);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
rect: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
||||||
|
ren.rect(msg.data.rect);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
fillRect: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
||||||
|
ren.fillRect(msg.data.rect);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
rects: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.rects) return {error: "Missing rects"};
|
||||||
|
ren.rects(msg.data.rects);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
lineTo: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.a || !msg.data.b) return {error: "Missing points a and b"};
|
||||||
|
ren.lineTo(msg.data.a, msg.data.b);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
texture: function(msg) {
|
||||||
|
if (!msg.data) return {error: "Missing texture data"};
|
||||||
|
var tex_id = msg.data.texture_id;
|
||||||
|
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||||
|
ren.texture(
|
||||||
|
resources.texture[tex_id],
|
||||||
|
msg.data.src,
|
||||||
|
msg.data.dst,
|
||||||
|
msg.data.angle || 0,
|
||||||
|
msg.data.anchor || {x:0.5, y:0.5}
|
||||||
|
);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
copyTexture: function(msg) {
|
||||||
|
if (!msg.data) return {error: "Missing texture data"};
|
||||||
|
var tex_id = msg.data.texture_id;
|
||||||
|
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||||
|
var tex = resources.texture[tex_id];
|
||||||
|
|
||||||
|
// Use the texture method with normalized coordinates
|
||||||
|
ren.texture(
|
||||||
|
tex,
|
||||||
|
msg.data.src || {x:0, y:0, width:tex.width, height:tex.height},
|
||||||
|
msg.data.dest || {x:0, y:0, width:tex.width, height:tex.height},
|
||||||
|
0, // No rotation
|
||||||
|
{x:0, y:0} // Top-left anchor
|
||||||
|
);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
sprite: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"};
|
||||||
|
ren.sprite(msg.data.sprite);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
geometry: function(msg) {
|
||||||
|
if (!msg.data) return {error: "Missing geometry data"};
|
||||||
|
var tex_id = msg.data.texture_id;
|
||||||
|
var tex = tex_id ? resources.texture[tex_id] : null;
|
||||||
|
ren.geometry(tex, msg.data.geometry);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
geometry_raw: function geometry_raw(msg) {
|
||||||
|
var geom = msg.data
|
||||||
|
ren.geometry_raw(resources.texture[geom.texture_id], geom.xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices);
|
||||||
|
},
|
||||||
|
|
||||||
|
debugText: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.text) return {error: "Missing text"};
|
||||||
|
ren.debugText([msg.data.pos.x, msg.data.pos.y], msg.data.text);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
clipEnabled: function(msg) {
|
||||||
|
return {data: ren.clipEnabled()};
|
||||||
|
},
|
||||||
|
|
||||||
|
texture9Grid: function(msg) {
|
||||||
|
if (!msg.data) return {error: "Missing data"};
|
||||||
|
var tex_id = msg.data.texture_id;
|
||||||
|
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||||
|
ren.texture9Grid(
|
||||||
|
resources.texture[tex_id],
|
||||||
|
msg.data.src,
|
||||||
|
msg.data.leftWidth,
|
||||||
|
msg.data.rightWidth,
|
||||||
|
msg.data.topHeight,
|
||||||
|
msg.data.bottomHeight,
|
||||||
|
msg.data.scale,
|
||||||
|
msg.data.dst
|
||||||
|
);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
textureTiled: function(msg) {
|
||||||
|
if (!msg.data) return {error: "Missing data"};
|
||||||
|
var tex_id = msg.data.texture_id;
|
||||||
|
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||||
|
ren.textureTiled(
|
||||||
|
resources.texture[tex_id],
|
||||||
|
msg.data.src,
|
||||||
|
msg.data.scale || 1.0,
|
||||||
|
msg.data.dst
|
||||||
|
);
|
||||||
|
return {success: true};
|
||||||
|
},
|
||||||
|
|
||||||
|
readPixels: function(msg) {
|
||||||
|
var surf = ren.readPixels(msg.data ? msg.data.rect : null);
|
||||||
|
if (!surf) return {error: "Failed to read pixels"};
|
||||||
|
var surf_id = allocate_id();
|
||||||
|
resources.surface[surf_id] = surf;
|
||||||
|
return {id: surf_id};
|
||||||
|
},
|
||||||
|
|
||||||
|
loadTexture: function(msg) {
|
||||||
|
if (!msg.data) throw new Error("Missing data")
|
||||||
|
|
||||||
|
var tex;
|
||||||
|
// Direct surface data
|
||||||
|
var surf = new surface(msg.data)
|
||||||
|
|
||||||
|
if (!surf)
|
||||||
|
throw new Error("Must provide surface_id or surface data")
|
||||||
|
|
||||||
|
tex = ren.load_texture(surf);
|
||||||
|
|
||||||
|
if (!tex) throw new Error("Failed to load texture")
|
||||||
|
var tex_id = allocate_id();
|
||||||
|
resources.texture[tex_id] = tex;
|
||||||
|
return {
|
||||||
|
id: tex_id,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
coordsFromWindow: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
||||||
|
return {data: ren.coordsFromWindow(msg.data.pos)};
|
||||||
|
},
|
||||||
|
|
||||||
|
coordsToWindow: function(msg) {
|
||||||
|
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
||||||
|
return {data: ren.coordsToWindow(msg.data.pos)};
|
||||||
|
},
|
||||||
|
|
||||||
|
batch: function(msg) {
|
||||||
|
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
|
||||||
|
|
||||||
|
for (var i = 0; i < msg.data.length; i++)
|
||||||
|
handle_renderer(msg.data[i]);
|
||||||
|
|
||||||
|
return {success:true};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Renderer operations
|
// Renderer operations
|
||||||
function handle_renderer(msg) {
|
function handle_renderer(msg) {
|
||||||
// Special case: createWindowAndRenderer creates both
|
if (!ren) return{reason:'no renderer!'}
|
||||||
if (msg.op === 'createWindowAndRenderer') {
|
|
||||||
var data = msg.data || {};
|
|
||||||
var result = prosperon.endowments.createWindowAndRenderer(
|
|
||||||
data.title || "Prosperon Window",
|
|
||||||
data.width || 640,
|
|
||||||
data.height || 480,
|
|
||||||
data.flags || 0
|
|
||||||
);
|
|
||||||
var win_id = allocate_id();
|
|
||||||
var ren_id = allocate_id();
|
|
||||||
resources.window[win_id] = result.window;
|
|
||||||
resources.renderer[ren_id] = result.renderer;
|
|
||||||
return {window_id: win_id, renderer_id: ren_id};
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other operations require a valid renderer ID
|
|
||||||
if (!msg.id || !resources.renderer[msg.id]) {
|
|
||||||
return {error: "Invalid renderer id: " + msg.id};
|
|
||||||
}
|
|
||||||
|
|
||||||
var ren = resources.renderer[msg.id];
|
|
||||||
|
|
||||||
switch (msg.op) {
|
|
||||||
case 'destroy':
|
|
||||||
delete resources.renderer[msg.id];
|
|
||||||
// Renderer is automatically destroyed when all references are gone
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'clear':
|
|
||||||
ren.clear();
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'present':
|
|
||||||
ren.present();
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'flush':
|
|
||||||
ren.flush();
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'get':
|
|
||||||
var prop = msg.data ? msg.data.property : null;
|
|
||||||
if (!prop) return {error: "Missing property name"};
|
|
||||||
|
|
||||||
// Handle special cases
|
|
||||||
if (prop === 'window') {
|
|
||||||
var win = ren.window;
|
|
||||||
if (!win) return {data: null};
|
|
||||||
// Find window ID
|
|
||||||
for (var id in resources.window) {
|
|
||||||
if (resources.window[id] === win) {
|
|
||||||
return {data: id};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Window not tracked, add it
|
|
||||||
var win_id = allocate_id();
|
|
||||||
resources.window[win_id] = win;
|
|
||||||
return {data: win_id};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle special getters that might return objects
|
|
||||||
if (prop === 'drawColor') {
|
|
||||||
var color = ren[prop];
|
|
||||||
if (color && typeof color === 'object') {
|
|
||||||
// Convert color object to array format [r,g,b,a]
|
|
||||||
return {data: [color.r || 0, color.g || 0, color.b || 0, color.a || 255]};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {data: ren[prop]};
|
|
||||||
|
|
||||||
case 'set':
|
|
||||||
var prop = msg.prop
|
|
||||||
var value = msg.value
|
|
||||||
if (!prop) return {error: "Missing property name"};
|
|
||||||
|
|
||||||
// Validate property is settable
|
|
||||||
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
|
|
||||||
if (readonly.indexOf(prop) !== -1) {
|
|
||||||
return {error: "Property '" + prop + "' is read-only"};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special handling for render target
|
|
||||||
if (prop === 'target' && value !== null && value !== undefined) {
|
|
||||||
var tex = resources.texture[value];
|
|
||||||
if (!tex) return {error: "Invalid texture id"};
|
|
||||||
value = tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ren[prop] = value;
|
var func = renderfuncs[msg.op];
|
||||||
return {success: true};
|
if (func) {
|
||||||
|
return func(msg);
|
||||||
case 'line':
|
} else {
|
||||||
if (!msg.data || !msg.data.points) return {error: "Missing points array"};
|
return {error: "Unknown renderer operation: " + msg.op};
|
||||||
ren.line(msg.data.points);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'point':
|
|
||||||
if (!msg.data || !msg.data.points) return {error: "Missing points"};
|
|
||||||
ren.point(msg.data.points);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'rect':
|
|
||||||
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
|
||||||
ren.rect(msg.data.rect);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'fillRect':
|
|
||||||
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
|
|
||||||
ren.fillRect(msg.data.rect);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'rects':
|
|
||||||
if (!msg.data || !msg.data.rects) return {error: "Missing rects"};
|
|
||||||
ren.rects(msg.data.rects);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'lineTo':
|
|
||||||
if (!msg.data || !msg.data.a || !msg.data.b) return {error: "Missing points a and b"};
|
|
||||||
ren.lineTo(msg.data.a, msg.data.b);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'texture':
|
|
||||||
if (!msg.data) return {error: "Missing texture data"};
|
|
||||||
var tex_id = msg.data.texture_id;
|
|
||||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
|
||||||
ren.texture(
|
|
||||||
resources.texture[tex_id],
|
|
||||||
msg.data.src,
|
|
||||||
msg.data.dst,
|
|
||||||
msg.data.angle || 0,
|
|
||||||
msg.data.anchor || {x:0.5, y:0.5}
|
|
||||||
);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'copyTexture':
|
|
||||||
if (!msg.data) return {error: "Missing texture data"};
|
|
||||||
var tex_id = msg.data.texture_id;
|
|
||||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
|
||||||
var tex = resources.texture[tex_id];
|
|
||||||
|
|
||||||
// Use the texture method with normalized coordinates
|
|
||||||
ren.texture(
|
|
||||||
tex,
|
|
||||||
msg.data.src || {x:0, y:0, width:tex.width, height:tex.height},
|
|
||||||
msg.data.dest || {x:0, y:0, width:tex.width, height:tex.height},
|
|
||||||
0, // No rotation
|
|
||||||
{x:0, y:0} // Top-left anchor
|
|
||||||
);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'sprite':
|
|
||||||
if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"};
|
|
||||||
ren.sprite(msg.data.sprite);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'geometry':
|
|
||||||
if (!msg.data) return {error: "Missing geometry data"};
|
|
||||||
var tex_id = msg.data.texture_id;
|
|
||||||
var tex = tex_id ? resources.texture[tex_id] : null;
|
|
||||||
ren.geometry(tex, msg.data.geometry);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'debugText':
|
|
||||||
if (!msg.data || !msg.data.text) return {error: "Missing text"};
|
|
||||||
ren.debugText([msg.data.pos.x, msg.data.pos.y], msg.data.text);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'clipEnabled':
|
|
||||||
return {data: ren.clipEnabled()};
|
|
||||||
|
|
||||||
case 'texture9Grid':
|
|
||||||
if (!msg.data) return {error: "Missing data"};
|
|
||||||
var tex_id = msg.data.texture_id;
|
|
||||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
|
||||||
ren.texture9Grid(
|
|
||||||
resources.texture[tex_id],
|
|
||||||
msg.data.src,
|
|
||||||
msg.data.leftWidth,
|
|
||||||
msg.data.rightWidth,
|
|
||||||
msg.data.topHeight,
|
|
||||||
msg.data.bottomHeight,
|
|
||||||
msg.data.scale,
|
|
||||||
msg.data.dst
|
|
||||||
);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'textureTiled':
|
|
||||||
if (!msg.data) return {error: "Missing data"};
|
|
||||||
var tex_id = msg.data.texture_id;
|
|
||||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
|
||||||
ren.textureTiled(
|
|
||||||
resources.texture[tex_id],
|
|
||||||
msg.data.src,
|
|
||||||
msg.data.scale || 1.0,
|
|
||||||
msg.data.dst
|
|
||||||
);
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'readPixels':
|
|
||||||
var surf = ren.readPixels(msg.data ? msg.data.rect : null);
|
|
||||||
if (!surf) return {error: "Failed to read pixels"};
|
|
||||||
var surf_id = allocate_id();
|
|
||||||
resources.surface[surf_id] = surf;
|
|
||||||
return {id: surf_id};
|
|
||||||
|
|
||||||
case 'loadTexture':
|
|
||||||
if (!msg.data) throw new Error("Missing data")
|
|
||||||
|
|
||||||
var tex;
|
|
||||||
// Direct surface data
|
|
||||||
var surf = new surface(msg.data)
|
|
||||||
|
|
||||||
if (!surf)
|
|
||||||
throw new Error("Must provide surface_id or surface data")
|
|
||||||
|
|
||||||
tex = ren.load_texture(surf);
|
|
||||||
|
|
||||||
if (!tex) throw new Error("Failed to load texture")
|
|
||||||
var tex_id = allocate_id();
|
|
||||||
resources.texture[tex_id] = tex;
|
|
||||||
return {
|
|
||||||
id: tex_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'flush':
|
|
||||||
ren.flush();
|
|
||||||
return {success: true};
|
|
||||||
|
|
||||||
case 'coordsFromWindow':
|
|
||||||
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
|
||||||
return {data: ren.coordsFromWindow(msg.data.pos)};
|
|
||||||
|
|
||||||
case 'coordsToWindow':
|
|
||||||
if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
|
|
||||||
return {data: ren.coordsToWindow(msg.data.pos)};
|
|
||||||
|
|
||||||
case 'batch':
|
|
||||||
// Execute a batch of operations
|
|
||||||
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
|
|
||||||
|
|
||||||
var results = [];
|
|
||||||
for (var i = 0; i < msg.data.length; i++) {
|
|
||||||
var cmd = msg.data[i];
|
|
||||||
if (!cmd.op) {
|
|
||||||
results.push({error: "Command at index " + i + " missing op"});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a temporary message object for the command
|
|
||||||
var temp_msg = {
|
|
||||||
kind: 'renderer',
|
|
||||||
id: msg.id,
|
|
||||||
op: cmd.op,
|
|
||||||
prop: cmd.prop,
|
|
||||||
value: cmd.value,
|
|
||||||
data: cmd.data
|
|
||||||
};
|
|
||||||
|
|
||||||
// Recursively call handle_renderer for each command
|
|
||||||
var result = handle_renderer(temp_msg);
|
|
||||||
results.push(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {results: results};
|
|
||||||
|
|
||||||
default:
|
|
||||||
return {error: "Unknown renderer operation: " + msg.op};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Texture operations
|
// Texture operations
|
||||||
function handle_texture(msg) {
|
function handle_texture(msg) {
|
||||||
// Special case: create needs a renderer
|
// Special case: create needs a renderer
|
||||||
if (msg.op === 'create') {
|
if (msg.op == 'create') {
|
||||||
if (!msg.data) return {error: "Missing texture data"};
|
if (!msg.data) return {error: "Missing texture data"};
|
||||||
var ren_id = msg.data.renderer_id;
|
var ren_id = msg.data.renderer_id;
|
||||||
if (!ren_id || !resources.renderer[ren_id]) return {error: "Invalid renderer id"};
|
if (!ren_id || !resources.renderer[ren_id]) return {error: "Invalid renderer id"};
|
||||||
@@ -511,11 +480,11 @@ function handle_texture(msg) {
|
|||||||
if (msg.data.surface_id) {
|
if (msg.data.surface_id) {
|
||||||
var surf = resources.surface[msg.data.surface_id];
|
var surf = resources.surface[msg.data.surface_id];
|
||||||
if (!surf) return {error: "Invalid surface id"};
|
if (!surf) return {error: "Invalid surface id"};
|
||||||
tex = new prosperon.endowments.texture(renderer, surf);
|
tex = new video.texture(renderer, surf);
|
||||||
}
|
}
|
||||||
// Create from properties
|
// Create from properties
|
||||||
else if (msg.data.width && msg.data.height) {
|
else if (msg.data.width && msg.data.height) {
|
||||||
tex = new prosperon.endowments.texture(renderer, {
|
tex = new video.texture(renderer, {
|
||||||
width: msg.data.width,
|
width: msg.data.width,
|
||||||
height: msg.data.height,
|
height: msg.data.height,
|
||||||
format: msg.data.format || 'rgba8888',
|
format: msg.data.format || 'rgba8888',
|
||||||
@@ -553,12 +522,12 @@ function handle_texture(msg) {
|
|||||||
|
|
||||||
case 'set':
|
case 'set':
|
||||||
var prop = msg.data ? msg.data.property : null;
|
var prop = msg.data ? msg.data.property : null;
|
||||||
var value = msg.data ? msg.data.value : undefined;
|
var value = msg.data ? msg.data.value : null;
|
||||||
if (!prop) return {error: "Missing property name"};
|
if (!prop) return {error: "Missing property name"};
|
||||||
|
|
||||||
// Validate property is settable
|
// Validate property is settable
|
||||||
var readonly = ['size', 'width', 'height'];
|
var readonly = ['size', 'width', 'height'];
|
||||||
if (readonly.indexOf(prop) !== -1) {
|
if (readonly.indexOf(prop) != -1) {
|
||||||
return {error: "Property '" + prop + "' is read-only"};
|
return {error: "Property '" + prop + "' is read-only"};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,7 +581,7 @@ function handle_cursor(msg) {
|
|||||||
var surf = new surface(msg.data)
|
var surf = new surface(msg.data)
|
||||||
|
|
||||||
var hotspot = msg.data.hotspot || [0, 0];
|
var hotspot = msg.data.hotspot || [0, 0];
|
||||||
var cursor = prosperon.endowments.createCursor(surf, hotspot);
|
var cursor = video.createCursor(surf, hotspot);
|
||||||
|
|
||||||
var cursor_id = allocate_id();
|
var cursor_id = allocate_id();
|
||||||
resources.cursor[cursor_id] = cursor;
|
resources.cursor[cursor_id] = cursor;
|
||||||
@@ -623,7 +592,7 @@ function handle_cursor(msg) {
|
|||||||
if (msg.id && resources.cursor[msg.id]) {
|
if (msg.id && resources.cursor[msg.id]) {
|
||||||
cursor = resources.cursor[msg.id];
|
cursor = resources.cursor[msg.id];
|
||||||
}
|
}
|
||||||
prosperon.endowments.setCursor(cursor);
|
video.setCursor(cursor);
|
||||||
return {success: true};
|
return {success: true};
|
||||||
|
|
||||||
case 'destroy':
|
case 'destroy':
|
||||||
@@ -643,16 +612,16 @@ prosperon.endowments = prosperon.endowments || {};
|
|||||||
|
|
||||||
// Mouse operations
|
// Mouse operations
|
||||||
function handle_mouse(msg) {
|
function handle_mouse(msg) {
|
||||||
var mouse = prosperon.endowments.mouse;
|
var mouse = video.mouse;
|
||||||
|
|
||||||
switch (msg.op) {
|
switch (msg.op) {
|
||||||
case 'show':
|
case 'show':
|
||||||
if (msg.data === undefined) return {error: "Missing show parameter"};
|
if (msg.data == null) return {error: "Missing show parameter"};
|
||||||
mouse.show(msg.data);
|
mouse.show(msg.data);
|
||||||
return {success: true};
|
return {success: true};
|
||||||
|
|
||||||
case 'capture':
|
case 'capture':
|
||||||
if (msg.data === undefined) return {error: "Missing capture parameter"};
|
if (msg.data == null) return {error: "Missing capture parameter"};
|
||||||
mouse.capture(msg.data);
|
mouse.capture(msg.data);
|
||||||
return {success: true};
|
return {success: true};
|
||||||
|
|
||||||
@@ -686,7 +655,7 @@ function handle_mouse(msg) {
|
|||||||
if (!cursor) return {data: null};
|
if (!cursor) return {data: null};
|
||||||
// Find or create cursor ID
|
// Find or create cursor ID
|
||||||
for (var id in resources.cursor) {
|
for (var id in resources.cursor) {
|
||||||
if (resources.cursor[id] === cursor) {
|
if (resources.cursor[id] == cursor) {
|
||||||
return {data: id};
|
return {data: id};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,7 +669,7 @@ function handle_mouse(msg) {
|
|||||||
if (!cursor) return {data: null};
|
if (!cursor) return {data: null};
|
||||||
// Find or create cursor ID
|
// Find or create cursor ID
|
||||||
for (var id in resources.cursor) {
|
for (var id in resources.cursor) {
|
||||||
if (resources.cursor[id] === cursor) {
|
if (resources.cursor[id] == cursor) {
|
||||||
return {data: id};
|
return {data: id};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,7 +679,7 @@ function handle_mouse(msg) {
|
|||||||
return {data: cursor_id};
|
return {data: cursor_id};
|
||||||
|
|
||||||
case 'create_system_cursor':
|
case 'create_system_cursor':
|
||||||
if (msg.data === undefined) return {error: "Missing cursor type"};
|
if (msg.data == null) return {error: "Missing cursor type"};
|
||||||
var cursor = mouse.create_system_cursor(msg.data);
|
var cursor = mouse.create_system_cursor(msg.data);
|
||||||
var cursor_id = allocate_id();
|
var cursor_id = allocate_id();
|
||||||
resources.cursor[cursor_id] = cursor;
|
resources.cursor[cursor_id] = cursor;
|
||||||
@@ -721,7 +690,7 @@ function handle_mouse(msg) {
|
|||||||
if (!window) return {data: null};
|
if (!window) return {data: null};
|
||||||
// Find window ID
|
// Find window ID
|
||||||
for (var id in resources.window) {
|
for (var id in resources.window) {
|
||||||
if (resources.window[id] === window) {
|
if (resources.window[id] == window) {
|
||||||
return {data: id};
|
return {data: id};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -737,7 +706,7 @@ function handle_mouse(msg) {
|
|||||||
|
|
||||||
// Keyboard operations
|
// Keyboard operations
|
||||||
function handle_keyboard(msg) {
|
function handle_keyboard(msg) {
|
||||||
var keyboard = prosperon.endowments.keyboard;
|
var keyboard = video.keyboard;
|
||||||
|
|
||||||
switch (msg.op) {
|
switch (msg.op) {
|
||||||
case 'get_state':
|
case 'get_state':
|
||||||
@@ -748,7 +717,7 @@ function handle_keyboard(msg) {
|
|||||||
if (!window) return {data: null};
|
if (!window) return {data: null};
|
||||||
// Find window ID
|
// Find window ID
|
||||||
for (var id in resources.window) {
|
for (var id in resources.window) {
|
||||||
if (resources.window[id] === window) {
|
if (resources.window[id] == window) {
|
||||||
return {data: id};
|
return {data: id};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ audio.cry = function cry(file) {
|
|||||||
if (!voice) return;
|
if (!voice) return;
|
||||||
return function() {
|
return function() {
|
||||||
voice.stop();
|
voice.stop();
|
||||||
voice = undefined;
|
voice = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
audio.cry[doc.sym] =
|
audio.cry[doc.sym] =
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* anim.js – drop this at top of your script or in a module */
|
/* anim.js – drop this at top of your script or in a module */
|
||||||
var Anim = (() => {
|
var Anim = (() => {
|
||||||
const DEFAULT_MIN = 1 / 60; /* 16 ms – one frame */
|
def DEFAULT_MIN = 1 / 60; /* 16 ms – one frame */
|
||||||
|
|
||||||
function play(source, loop=true){
|
function play(source, loop=true){
|
||||||
return {
|
return {
|
||||||
@@ -13,9 +13,9 @@ var Anim = (() => {
|
|||||||
|
|
||||||
function update(a, dt){
|
function update(a, dt){
|
||||||
a.timer += dt;
|
a.timer += dt;
|
||||||
const frames = a.src.frames;
|
def frames = a.src.frames;
|
||||||
while(true){
|
while(true){
|
||||||
const time = Math.max(frames[a.idx].time || 0, Anim.minDelay);
|
def time = Math.max(frames[a.idx].time || 0, Anim.minDelay);
|
||||||
if(a.timer < time) break; /* still on current frame */
|
if(a.timer < time) break; /* still on current frame */
|
||||||
|
|
||||||
a.timer -= time;
|
a.timer -= time;
|
||||||
@@ -29,7 +29,7 @@ var Anim = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function current(a){ return a.src.frames[a.idx].image; }
|
function current(a){ return a.src.frames[a.idx].image; }
|
||||||
function updateAll(arr, dt){ for(const a of arr) update(a, dt); }
|
function updateAll(arr, dt){ for(def a of arr) update(a, dt); }
|
||||||
function draw(a, pos, opt, pipe){
|
function draw(a, pos, opt, pipe){
|
||||||
draw2d.image(current(a), pos, 0, [0,0], [0,0], opt, pipe);
|
draw2d.image(current(a), pos, 0, [0,0], [0,0], opt, pipe);
|
||||||
}
|
}
|
||||||
@@ -53,17 +53,17 @@ var camera = {
|
|||||||
fov:50,
|
fov:50,
|
||||||
near_z: 0,
|
near_z: 0,
|
||||||
far_z: 1000,
|
far_z: 1000,
|
||||||
surface: undefined,
|
surface: null,
|
||||||
viewport: {x:0,y:0,width:1,height:1},
|
viewport: {x:0,y:0,width:1,height:1},
|
||||||
ortho:true,
|
ortho:true,
|
||||||
anchor:[0,0],
|
anchor:[0,0],
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── load animations ───────────────────── */
|
/* ── load animations ───────────────────── */
|
||||||
const crab = gfx.texture('tests/crab'); // gif → Animation
|
def crab = gfx.texture('tests/crab'); // gif → Animation
|
||||||
const warrior = gfx.texture('tests/warrior'); // ase → {Original:Animation}
|
def warrior = gfx.texture('tests/warrior'); // ase → {Original:Animation}
|
||||||
|
|
||||||
const anims = [
|
def anims = [
|
||||||
Anim.play(crab), // crab.frames
|
Anim.play(crab), // crab.frames
|
||||||
Anim.play(warrior.Run) // warrior.Original.frames
|
Anim.play(warrior.Run) // warrior.Original.frames
|
||||||
];
|
];
|
||||||
@@ -76,8 +76,8 @@ Anim.minDelay = 1 / 100; // 10 ms, feel free to tune later
|
|||||||
let last = os.now();
|
let last = os.now();
|
||||||
|
|
||||||
function loop(){
|
function loop(){
|
||||||
const now = os.now();
|
def now = os.now();
|
||||||
const dt = now - last; // real frame time
|
def dt = now - last; // real frame time
|
||||||
last = now;
|
last = now;
|
||||||
|
|
||||||
Anim.updateAll(anims, dt);
|
Anim.updateAll(anims, dt);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var camera = {
|
|||||||
fov:50,
|
fov:50,
|
||||||
near_z: 0,
|
near_z: 0,
|
||||||
far_z: 1000,
|
far_z: 1000,
|
||||||
surface: undefined,
|
surface: null,
|
||||||
viewport: {x:0,y:0,width:1,height:1},
|
viewport: {x:0,y:0,width:1,height:1},
|
||||||
ortho:true,
|
ortho:true,
|
||||||
anchor:[0,0],
|
anchor:[0,0],
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var camera = {
|
|||||||
fov:50,
|
fov:50,
|
||||||
near_z: 0,
|
near_z: 0,
|
||||||
far_z: 1000,
|
far_z: 1000,
|
||||||
surface: undefined,
|
surface: null,
|
||||||
viewport: {x:0,y:0,width:1,height:1},
|
viewport: {x:0,y:0,width:1,height:1},
|
||||||
ortho:true,
|
ortho:true,
|
||||||
anchor:[0.5,0.5],
|
anchor:[0.5,0.5],
|
||||||
@@ -31,7 +31,7 @@ var hudcam = {
|
|||||||
fov:50,
|
fov:50,
|
||||||
near_z: 0,
|
near_z: 0,
|
||||||
far_z: 1000,
|
far_z: 1000,
|
||||||
surface: undefined,
|
surface: null,
|
||||||
viewport: {x:0,y:0,width:1,height:1},
|
viewport: {x:0,y:0,width:1,height:1},
|
||||||
ortho:true,
|
ortho:true,
|
||||||
anchor:[0,0],
|
anchor:[0,0],
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ var json = use('json');
|
|||||||
|
|
||||||
// Get list of cameras
|
// Get list of cameras
|
||||||
var cameras = camera.list();
|
var cameras = camera.list();
|
||||||
if (cameras.length === 0) {
|
if (cameras.length == 0) {
|
||||||
log.console("No cameras found!");
|
log.console("No cameras found!");
|
||||||
$_. stop();
|
$_. stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var json = use('json');
|
|||||||
|
|
||||||
// Get first camera
|
// Get first camera
|
||||||
var cameras = camera.list();
|
var cameras = camera.list();
|
||||||
if (cameras.length === 0) {
|
if (cameras.length == 0) {
|
||||||
log.console("No cameras found!");
|
log.console("No cameras found!");
|
||||||
$_.stop();
|
$_.stop();
|
||||||
}
|
}
|
||||||
@@ -30,10 +30,10 @@ log.console(" Colorspace:", format.colorspace);
|
|||||||
// Handle camera approval
|
// Handle camera approval
|
||||||
var approved = false;
|
var approved = false;
|
||||||
$_.receiver(e => {
|
$_.receiver(e => {
|
||||||
if (e.type === 'camera_device_approved') {
|
if (e.type == 'camera_device_approved') {
|
||||||
log.console("\nCamera approved!");
|
log.console("\nCamera approved!");
|
||||||
approved = true;
|
approved = true;
|
||||||
} else if (e.type === 'camera_device_denied') {
|
} else if (e.type == 'camera_device_denied') {
|
||||||
log.error("Camera access denied!");
|
log.error("Camera access denied!");
|
||||||
$_.stop();
|
$_.stop();
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ function capture_test() {
|
|||||||
log.console("\nTesting colorspace conversions:");
|
log.console("\nTesting colorspace conversions:");
|
||||||
|
|
||||||
// Convert to sRGB if not already
|
// Convert to sRGB if not already
|
||||||
if (format.colorspace !== "srgb") {
|
if (format.colorspace != "srgb") {
|
||||||
try {
|
try {
|
||||||
var srgb_surf = surf.convert(surf.format, "srgb");
|
var srgb_surf = surf.convert(surf.format, "srgb");
|
||||||
log.console(" Converted to sRGB colorspace");
|
log.console(" Converted to sRGB colorspace");
|
||||||
@@ -89,7 +89,7 @@ function capture_test() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If YUV format, try BT.709 (HD video standard)
|
// If YUV format, try BT.709 (HD video standard)
|
||||||
if (surf.format.indexOf("yuv") !== -1 || surf.format.indexOf("yuy") !== -1) {
|
if (surf.format.indexOf("yuv") != -1 || surf.format.indexOf("yuy") != -1) {
|
||||||
try {
|
try {
|
||||||
var hd_surf = surf.convert(surf.format, "bt709_limited");
|
var hd_surf = surf.convert(surf.format, "bt709_limited");
|
||||||
log.console(" Converted to BT.709 limited (HD video standard)");
|
log.console(" Converted to BT.709 limited (HD video standard)");
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ if (cameras.length > 0) {
|
|||||||
// Try to find a 640x480 format
|
// Try to find a 640x480 format
|
||||||
var preferred_format = null;
|
var preferred_format = null;
|
||||||
for (var i = 0; i < formats.length; i++) {
|
for (var i = 0; i < formats.length; i++) {
|
||||||
if (formats[i].width === 640 && formats[i].height === 480) {
|
if (formats[i].width == 640 && formats[i].height == 480) {
|
||||||
preferred_format = formats[i];
|
preferred_format = formats[i];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ var webcam_texture = null;
|
|||||||
|
|
||||||
// Handle camera events
|
// Handle camera events
|
||||||
$_.receiver(e => {
|
$_.receiver(e => {
|
||||||
if (e.type === 'camera_device_approved' && e.which === cam_id) {
|
if (e.type == 'camera_device_approved' && e.which == cam_id) {
|
||||||
log.console("Camera approved!");
|
log.console("Camera approved!");
|
||||||
cam_approved = true;
|
cam_approved = true;
|
||||||
} else if (e.type === 'camera_device_denied' && e.which === cam_id) {
|
} else if (e.type == 'camera_device_denied' && e.which == cam_id) {
|
||||||
log.error("Camera access denied!");
|
log.error("Camera access denied!");
|
||||||
$_.stop();
|
$_.stop();
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ send(video_actor, {
|
|||||||
|
|
||||||
// List available cameras
|
// List available cameras
|
||||||
var cameras = camera.list();
|
var cameras = camera.list();
|
||||||
if (cameras.length === 0) {
|
if (cameras.length == 0) {
|
||||||
log.error("No cameras found!");
|
log.error("No cameras found!");
|
||||||
log.console(json.encode(cameras))
|
log.console(json.encode(cameras))
|
||||||
$_.stop();
|
$_.stop();
|
||||||
@@ -89,10 +89,10 @@ send(video_actor, {
|
|||||||
// Look for a 640x480 format with preferred colorspace
|
// Look for a 640x480 format with preferred colorspace
|
||||||
var preferred_format = null;
|
var preferred_format = null;
|
||||||
for (var i = 0; i < formats.length; i++) {
|
for (var i = 0; i < formats.length; i++) {
|
||||||
if (formats[i].width === 640 && formats[i].height === 480) {
|
if (formats[i].width == 640 && formats[i].height == 480) {
|
||||||
preferred_format = formats[i];
|
preferred_format = formats[i];
|
||||||
// Prefer JPEG or sRGB colorspace if available
|
// Prefer JPEG or sRGB colorspace if available
|
||||||
if (formats[i].colorspace === "jpeg" || formats[i].colorspace === "srgb") {
|
if (formats[i].colorspace == "jpeg" || formats[i].colorspace == "srgb") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,10 +38,9 @@ $_.delay($_.stop, 3)
|
|||||||
|
|
||||||
var os = use('os')
|
var os = use('os')
|
||||||
var actor = use('actor')
|
var actor = use('actor')
|
||||||
var ioguy = {
|
var ioguy = {}
|
||||||
__ACTORDATA__: {
|
ioguy[cell.actor_sym] = {
|
||||||
id: actor.ioactor()
|
id: actor.ioactor()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send(ioguy, {
|
send(ioguy, {
|
||||||
@@ -50,7 +49,7 @@ send(ioguy, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$_.receiver(e => {
|
$_.receiver(e => {
|
||||||
if (e.type === 'quit')
|
if (e.type == 'quit')
|
||||||
os.exit()
|
os.exit()
|
||||||
else
|
else
|
||||||
log.console(json.encode(e))
|
log.console(json.encode(e))
|
||||||
|
|||||||
78
prosperon/tilemap.cm
Normal file
78
prosperon/tilemap.cm
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// tilemap
|
||||||
|
|
||||||
|
function tilemap()
|
||||||
|
{
|
||||||
|
this.tiles = [];
|
||||||
|
this.offset_x = 0;
|
||||||
|
this.offset_y = 0;
|
||||||
|
this.size_x = 32;
|
||||||
|
this.size_y = 32;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
tilemap.for = function (map, fn) {
|
||||||
|
for (var x = 0; x < map.tiles.length; x++) {
|
||||||
|
if (!map.tiles[x]) continue;
|
||||||
|
for (var y = 0; y < map.tiles[x].length; y++) {
|
||||||
|
if (map.tiles[x][y] != null) {
|
||||||
|
var result = fn(map.tiles[x][y], {
|
||||||
|
x: x + map.offset_x,
|
||||||
|
y: y + map.offset_y
|
||||||
|
});
|
||||||
|
if (result != null) {
|
||||||
|
map.tiles[x][y] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tilemap.prototype =
|
||||||
|
{
|
||||||
|
at(pos) {
|
||||||
|
var x = pos.x - this.offset_x;
|
||||||
|
var y = pos.y - this.offset_y;
|
||||||
|
if (!this.tiles[x]) return null;
|
||||||
|
return this.tiles[x][y];
|
||||||
|
},
|
||||||
|
|
||||||
|
set(pos, image) {
|
||||||
|
// Shift arrays if negative indices
|
||||||
|
if (pos.x < this.offset_x) {
|
||||||
|
var shift = this.offset_x - pos.x;
|
||||||
|
var new_tiles = [];
|
||||||
|
for (var i = 0; i < shift; i++) new_tiles[i] = [];
|
||||||
|
this.tiles = new_tiles.concat(this.tiles);
|
||||||
|
this.offset_x = pos.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.y < this.offset_y) {
|
||||||
|
var shift = this.offset_y - pos.y;
|
||||||
|
for (var i = 0; i < this.tiles.length; i++) {
|
||||||
|
if (!this.tiles[i]) this.tiles[i] = [];
|
||||||
|
var new_col = [];
|
||||||
|
for (var j = 0; j < shift; j++) new_col[j] = null;
|
||||||
|
this.tiles[i] = new_col.concat(this.tiles[i]);
|
||||||
|
}
|
||||||
|
this.offset_y = pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = pos.x - this.offset_x;
|
||||||
|
var y = pos.y - this.offset_y;
|
||||||
|
|
||||||
|
// Ensure array exists up to x
|
||||||
|
while (this.tiles.length <= x) this.tiles.push([]);
|
||||||
|
|
||||||
|
// Set the value
|
||||||
|
this.tiles[x][y] = image;
|
||||||
|
},
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
return {
|
||||||
|
cmd:'tilemap',
|
||||||
|
tilemap:this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return tilemap
|
||||||
@@ -43,15 +43,15 @@ Ease.quint = make_easing_fns(5)
|
|||||||
|
|
||||||
Ease.expo = {
|
Ease.expo = {
|
||||||
in(t) {
|
in(t) {
|
||||||
return t === 0 ? 0 : Math.pow(2, 10 * t - 10)
|
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
|
||||||
},
|
},
|
||||||
out(t) {
|
out(t) {
|
||||||
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
||||||
},
|
},
|
||||||
inout(t) {
|
inout(t) {
|
||||||
return t === 0
|
return t == 0
|
||||||
? 0
|
? 0
|
||||||
: t === 1
|
: t == 1
|
||||||
? 1
|
? 1
|
||||||
: t < 0.5
|
: t < 0.5
|
||||||
? Math.pow(2, 20 * t - 10) / 2
|
? Math.pow(2, 20 * t - 10) / 2
|
||||||
@@ -93,26 +93,26 @@ Ease.sine = {
|
|||||||
|
|
||||||
Ease.elastic = {
|
Ease.elastic = {
|
||||||
in(t) {
|
in(t) {
|
||||||
return t === 0
|
return t == 0
|
||||||
? 0
|
? 0
|
||||||
: t === 1
|
: t == 1
|
||||||
? 1
|
? 1
|
||||||
: -Math.pow(2, 10 * t - 10) *
|
: -Math.pow(2, 10 * t - 10) *
|
||||||
Math.sin((t * 10 - 10.75) * this.c4)
|
Math.sin((t * 10 - 10.75) * this.c4)
|
||||||
},
|
},
|
||||||
out(t) {
|
out(t) {
|
||||||
return t === 0
|
return t == 0
|
||||||
? 0
|
? 0
|
||||||
: t === 1
|
: t == 1
|
||||||
? 1
|
? 1
|
||||||
: Math.pow(2, -10 * t) *
|
: Math.pow(2, -10 * t) *
|
||||||
Math.sin((t * 10 - 0.75) * this.c4) +
|
Math.sin((t * 10 - 0.75) * this.c4) +
|
||||||
1
|
1
|
||||||
},
|
},
|
||||||
inout(t) {
|
inout(t) {
|
||||||
t === 0
|
t == 0
|
||||||
? 0
|
? 0
|
||||||
: t === 1
|
: t == 1
|
||||||
? 1
|
? 1
|
||||||
: t < 0.5
|
: t < 0.5
|
||||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
|
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
|
||||||
@@ -128,10 +128,10 @@ var tween = function (from, to, time, fn, cb) {
|
|||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
stop()
|
stop()
|
||||||
fn = undefined
|
fn = null
|
||||||
stop = undefined
|
stop = null
|
||||||
cb = undefined
|
cb = null
|
||||||
update = undefined
|
update = null
|
||||||
}
|
}
|
||||||
|
|
||||||
var update = function tween_update(dt) {
|
var update = function tween_update(dt) {
|
||||||
@@ -159,8 +159,8 @@ var Tween = {
|
|||||||
var defn = Object.create(this.default)
|
var defn = Object.create(this.default)
|
||||||
Object.assign(defn, options)
|
Object.assign(defn, options)
|
||||||
|
|
||||||
if (defn.loop === "circle") tvals.push(tvals[0])
|
if (defn.loop == "circle") tvals.push(tvals[0])
|
||||||
else if (defn.loop === "yoyo") {
|
else if (defn.loop == "yoyo") {
|
||||||
for (var i = tvals.length - 2; i >= 0; i--) tvals.push(tvals[i])
|
for (var i = tvals.length - 2; i >= 0; i--) tvals.push(tvals[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,15 +170,15 @@ var Tween = {
|
|||||||
|
|
||||||
defn.fn = function (dt) {
|
defn.fn = function (dt) {
|
||||||
defn.accum += dt
|
defn.accum += dt
|
||||||
if (defn.accum >= defn.time && defn.loop === "hold") {
|
if (defn.accum >= defn.time && defn.loop == "hold") {
|
||||||
if (typeof target === "string") obj[target] = tvals[tvals.length - 1]
|
if (typeof target == "string") obj[target] = tvals[tvals.length - 1]
|
||||||
else target(tvals[tvals.length - 1])
|
else target(tvals[tvals.length - 1])
|
||||||
defn.pause()
|
defn.pause()
|
||||||
defn.cb.call(obj)
|
defn.cb.call(obj)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defn.pct = (defn.accum % defn.time) / defn.time
|
defn.pct = (defn.accum % defn.time) / defn.time
|
||||||
if (defn.loop === "none" && defn.accum >= defn.time) defn.stop()
|
if (defn.loop == "none" && defn.accum >= defn.time) defn.stop()
|
||||||
|
|
||||||
var t = defn.whole ? defn.ease(defn.pct) : defn.pct
|
var t = defn.whole ? defn.ease(defn.pct) : defn.pct
|
||||||
var nval = t / slicelen
|
var nval = t / slicelen
|
||||||
@@ -186,7 +186,7 @@ var Tween = {
|
|||||||
nval -= i
|
nval -= i
|
||||||
if (!defn.whole) nval = defn.ease(nval)
|
if (!defn.whole) nval = defn.ease(nval)
|
||||||
|
|
||||||
if (typeof target === "string") obj[target] = tvals[i].lerp(tvals[i + 1], nval)
|
if (typeof target == "string") obj[target] = tvals[i].lerp(tvals[i + 1], nval)
|
||||||
else target(tvals[i].lerp(tvals[i + 1], nval))
|
else target(tvals[i].lerp(tvals[i + 1], nval))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ var Tween = {
|
|||||||
}
|
}
|
||||||
defn.restart = function () {
|
defn.restart = function () {
|
||||||
defn.accum = 0
|
defn.accum = 0
|
||||||
if (typeof target === "string") obj[target] = tvals[0]
|
if (typeof target == "string") obj[target] = tvals[0]
|
||||||
else target(tvals[0])
|
else target(tvals[0])
|
||||||
}
|
}
|
||||||
defn.stop = function () {
|
defn.stop = function () {
|
||||||
|
|||||||
1446
scripts/base.cm
1446
scripts/base.cm
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,6 @@ log.console("Cleaning build artifacts...")
|
|||||||
// Remove the build directory
|
// Remove the build directory
|
||||||
try {
|
try {
|
||||||
io.rmdir('.cell/build')
|
io.rmdir('.cell/build')
|
||||||
remove_dir('.cell/build')
|
|
||||||
log.console("Build directory removed")
|
log.console("Build directory removed")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("Failed during cleanup: " + e)
|
log.error("Failed during cleanup: " + e)
|
||||||
|
|||||||
249
scripts/config.ce
Normal file
249
scripts/config.ce
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// cell config - Manage system and actor configurations
|
||||||
|
|
||||||
|
var io = use('io')
|
||||||
|
var toml = use('toml')
|
||||||
|
var shop = use('shop')
|
||||||
|
var text = use('text')
|
||||||
|
|
||||||
|
function print_help() {
|
||||||
|
log.console("Usage: cell config <command> [options]")
|
||||||
|
log.console("")
|
||||||
|
log.console("Commands:")
|
||||||
|
log.console(" get <key> Get a configuration value")
|
||||||
|
log.console(" set <key> <value> Set a configuration value")
|
||||||
|
log.console(" list List all configurations")
|
||||||
|
log.console(" actor <name> get <key> Get actor-specific config")
|
||||||
|
log.console(" actor <name> set <key> <val> Set actor-specific config")
|
||||||
|
log.console(" actor <name> list List actor configurations")
|
||||||
|
log.console("")
|
||||||
|
log.console("Examples:")
|
||||||
|
log.console(" cell config get system.ar_timer")
|
||||||
|
log.console(" cell config set system.net_service 0.2")
|
||||||
|
log.console(" cell config actor prosperon/_sdl_video set resolution 1920x1080")
|
||||||
|
log.console(" cell config actor extramath/spline set precision high")
|
||||||
|
log.console("")
|
||||||
|
log.console("System keys:")
|
||||||
|
log.console(" system.ar_timer - Seconds before idle actor reclamation")
|
||||||
|
log.console(" system.actor_memory - MB of memory an actor can use (0=unbounded)")
|
||||||
|
log.console(" system.net_service - Seconds per network service pull")
|
||||||
|
log.console(" system.reply_timeout - Seconds to hold callback for replies (0=unbounded)")
|
||||||
|
log.console(" system.actor_max - Max number of simultaneous actors")
|
||||||
|
log.console(" system.stack_max - MB of memory each actor's stack can grow to")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a dot-notation key into path segments
|
||||||
|
function parse_key(key) {
|
||||||
|
return key.split('.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a value from nested object using path
|
||||||
|
function get_nested(obj, path) {
|
||||||
|
var current = obj
|
||||||
|
for (var segment of path) {
|
||||||
|
if (!current || typeof current != 'object') return null
|
||||||
|
current = current[segment]
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a value in nested object using path
|
||||||
|
function set_nested(obj, path, value) {
|
||||||
|
var current = obj
|
||||||
|
for (var i = 0; i < path.length - 1; i++) {
|
||||||
|
var segment = path[i]
|
||||||
|
if (!current[segment] || typeof current[segment] != 'object') {
|
||||||
|
current[segment] = {}
|
||||||
|
}
|
||||||
|
current = current[segment]
|
||||||
|
}
|
||||||
|
current[path[path.length - 1]] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse value string into appropriate type
|
||||||
|
function parse_value(str) {
|
||||||
|
// Boolean
|
||||||
|
if (str == 'true') return true
|
||||||
|
if (str == 'false') return false
|
||||||
|
|
||||||
|
// Number (including underscores)
|
||||||
|
var num_str = str.replace(/_/g, '')
|
||||||
|
if (/^-?\d+$/.test(num_str)) return parseInt(num_str)
|
||||||
|
if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str)
|
||||||
|
|
||||||
|
// String
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format value for display
|
||||||
|
function format_value(val) {
|
||||||
|
if (typeof val == 'string') return '"' + val + '"'
|
||||||
|
if (typeof val == 'number' && val >= 1000) {
|
||||||
|
// Add underscores to large numbers
|
||||||
|
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '_')
|
||||||
|
}
|
||||||
|
return String(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print configuration tree recursively
|
||||||
|
function print_config(obj, prefix = '') {
|
||||||
|
for (var key in obj) {
|
||||||
|
var val = obj[key]
|
||||||
|
var full_key = prefix ? prefix + '.' + key : key
|
||||||
|
|
||||||
|
if (val && typeof val == 'object' && !Array.isArray(val)) {
|
||||||
|
print_config(val, full_key)
|
||||||
|
} else {
|
||||||
|
log.console(full_key + ' = ' + format_value(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main command handling
|
||||||
|
if (args.length == 0) {
|
||||||
|
print_help()
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = shop.load_config()
|
||||||
|
if (!config) {
|
||||||
|
log.error("Failed to load .cell/cell.toml")
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var command = args[0]
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'help':
|
||||||
|
case '-h':
|
||||||
|
case '--help':
|
||||||
|
print_help()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'list':
|
||||||
|
log.console("# Cell Configuration")
|
||||||
|
log.console("")
|
||||||
|
print_config(config)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'get':
|
||||||
|
if (args.length < 2) {
|
||||||
|
log.error("Usage: cell config get <key>")
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var key = args[1]
|
||||||
|
var path = parse_key(key)
|
||||||
|
var value = get_nested(config, path)
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
log.error("Key not found: " + key)
|
||||||
|
} else if (value && typeof value == 'object' && !Array.isArray(value)) {
|
||||||
|
// Print all nested values
|
||||||
|
print_config(value, key)
|
||||||
|
} else {
|
||||||
|
log.console(key + ' = ' + format_value(value))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'set':
|
||||||
|
if (args.length < 3) {
|
||||||
|
log.error("Usage: cell config set <key> <value>")
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var key = args[1]
|
||||||
|
var value_str = args[2]
|
||||||
|
var path = parse_key(key)
|
||||||
|
var value = parse_value(value_str)
|
||||||
|
|
||||||
|
// Validate system keys
|
||||||
|
if (path[0] == 'system') {
|
||||||
|
var valid_system_keys = [
|
||||||
|
'ar_timer', 'actor_memory', 'net_service',
|
||||||
|
'reply_timeout', 'actor_max', 'stack_max'
|
||||||
|
]
|
||||||
|
if (!valid_system_keys.includes(path[1])) {
|
||||||
|
log.error("Invalid system key. Valid keys: " + valid_system_keys.join(', '))
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_nested(config, path, value)
|
||||||
|
shop.save_config(config)
|
||||||
|
log.console("Set " + key + " = " + format_value(value))
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'actor':
|
||||||
|
// Handle actor-specific configuration
|
||||||
|
if (args.length < 3) {
|
||||||
|
log.error("Usage: cell config actor <name> <command> [options]")
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var actor_name = args[1]
|
||||||
|
var actor_cmd = args[2]
|
||||||
|
|
||||||
|
// Initialize actors section if needed
|
||||||
|
config.actors = config.actors || {}
|
||||||
|
config.actors[actor_name] = config.actors[actor_name] || {}
|
||||||
|
|
||||||
|
switch (actor_cmd) {
|
||||||
|
case 'list':
|
||||||
|
if (Object.keys(config.actors[actor_name]).length == 0) {
|
||||||
|
log.console("No configuration for actor: " + actor_name)
|
||||||
|
} else {
|
||||||
|
log.console("# Configuration for actor: " + actor_name)
|
||||||
|
log.console("")
|
||||||
|
print_config(config.actors[actor_name], 'actors.' + actor_name)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'get':
|
||||||
|
if (args.length < 4) {
|
||||||
|
log.error("Usage: cell config actor <name> get <key>")
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var key = args[3]
|
||||||
|
var path = parse_key(key)
|
||||||
|
var value = get_nested(config.actors[actor_name], path)
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
log.error("Key not found for actor " + actor_name + ": " + key)
|
||||||
|
} else {
|
||||||
|
log.console('actors.' + actor_name + '.' + key + ' = ' + format_value(value))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'set':
|
||||||
|
if (args.length < 5) {
|
||||||
|
log.error("Usage: cell config actor <name> set <key> <value>")
|
||||||
|
$_.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var key = args[3]
|
||||||
|
var value_str = args[4]
|
||||||
|
var path = parse_key(key)
|
||||||
|
var value = parse_value(value_str)
|
||||||
|
|
||||||
|
set_nested(config.actors[actor_name], path, value)
|
||||||
|
shop.save_config(config)
|
||||||
|
log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.error("Unknown actor command: " + actor_cmd)
|
||||||
|
log.console("Valid commands: list, get, set")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.error("Unknown command: " + command)
|
||||||
|
print_help()
|
||||||
|
}
|
||||||
|
|
||||||
|
$_.stop()
|
||||||
@@ -5,20 +5,20 @@ function docOf(obj, prop) {
|
|||||||
|
|
||||||
// 1) If `block` is a string, that's the entire doc for `obj`.
|
// 1) If `block` is a string, that's the entire doc for `obj`.
|
||||||
// If a sub-property is requested, we have nowhere to look → return ''.
|
// If a sub-property is requested, we have nowhere to look → return ''.
|
||||||
if (typeof block === 'string') {
|
if (typeof block == 'string') {
|
||||||
return prop ? '' : block;
|
return prop ? '' : block;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Otherwise, if `block` is an object:
|
// 2) Otherwise, if `block` is an object:
|
||||||
// (a) With no `prop`, return block.doc or block[cell.DOC].
|
// (a) With no `prop`, return block.doc or block[cell.DOC].
|
||||||
// (b) If `prop` is given, look for doc specifically for that property (just one level).
|
// (b) If `prop` is given, look for doc specifically for that property (just one level).
|
||||||
if (typeof block === 'object') {
|
if (typeof block == 'object') {
|
||||||
// 2a) No property → top-level doc
|
// 2a) No property → top-level doc
|
||||||
if (!prop) {
|
if (!prop) {
|
||||||
if (typeof block.doc === 'string') {
|
if (typeof block.doc == 'string') {
|
||||||
return block.doc;
|
return block.doc;
|
||||||
}
|
}
|
||||||
if (typeof block[cell.DOC] === 'string') {
|
if (typeof block[cell.DOC] == 'string') {
|
||||||
return block[cell.DOC];
|
return block[cell.DOC];
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
@@ -27,14 +27,14 @@ function docOf(obj, prop) {
|
|||||||
// 2b) If a prop is requested → see if there's a doc string or object for that property
|
// 2b) If a prop is requested → see if there's a doc string or object for that property
|
||||||
var subBlock = block[prop];
|
var subBlock = block[prop];
|
||||||
if (!subBlock) return '';
|
if (!subBlock) return '';
|
||||||
if (typeof subBlock === 'string') {
|
if (typeof subBlock == 'string') {
|
||||||
return subBlock;
|
return subBlock;
|
||||||
}
|
}
|
||||||
if (typeof subBlock === 'object') {
|
if (typeof subBlock == 'object') {
|
||||||
if (typeof subBlock.doc === 'string') {
|
if (typeof subBlock.doc == 'string') {
|
||||||
return subBlock.doc;
|
return subBlock.doc;
|
||||||
}
|
}
|
||||||
if (typeof subBlock[cell.DOC] === 'string') {
|
if (typeof subBlock[cell.DOC] == 'string') {
|
||||||
return subBlock[cell.DOC];
|
return subBlock[cell.DOC];
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
@@ -94,7 +94,7 @@ function parseDocStr(docStr) {
|
|||||||
|
|
||||||
if (returnLines.length) {
|
if (returnLines.length) {
|
||||||
// If there were param lines, ensure blank line before the returns
|
// If there were param lines, ensure blank line before the returns
|
||||||
if (paramLines.length && returnLines[0] !== '') {
|
if (paramLines.length && returnLines[0] != '') {
|
||||||
final.push('');
|
final.push('');
|
||||||
}
|
}
|
||||||
final.push.apply(final, returnLines);
|
final.push.apply(final, returnLines);
|
||||||
@@ -131,14 +131,14 @@ function walkObject(obj, lines, level, name) {
|
|||||||
var propNames = Object.getOwnPropertyNames(obj);
|
var propNames = Object.getOwnPropertyNames(obj);
|
||||||
for (var i = 0; i < propNames.length; i++) {
|
for (var i = 0; i < propNames.length; i++) {
|
||||||
var prop = propNames[i];
|
var prop = propNames[i];
|
||||||
if (prop === 'constructor') continue;
|
if (prop == 'constructor') continue;
|
||||||
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(obj, prop);
|
var desc = Object.getOwnPropertyDescriptor(obj, prop);
|
||||||
if (!desc) continue;
|
if (!desc) continue;
|
||||||
|
|
||||||
// Check if accessor property (getter/setter)
|
// Check if accessor property (getter/setter)
|
||||||
var hasGetter = typeof desc.get === 'function';
|
var hasGetter = typeof desc.get == 'function';
|
||||||
var hasSetter = typeof desc.set === 'function';
|
var hasSetter = typeof desc.set == 'function';
|
||||||
|
|
||||||
if (hasGetter || hasSetter) {
|
if (hasGetter || hasSetter) {
|
||||||
writeProperty(lines, obj, prop, desc, level);
|
writeProperty(lines, obj, prop, desc, level);
|
||||||
@@ -150,11 +150,11 @@ function walkObject(obj, lines, level, name) {
|
|||||||
var val = desc.value;
|
var val = desc.value;
|
||||||
|
|
||||||
// If it's a function, treat it like a method
|
// If it's a function, treat it like a method
|
||||||
if (typeof val === 'function') {
|
if (typeof val == 'function') {
|
||||||
writeMethod(lines, obj, prop, val, level);
|
writeMethod(lines, obj, prop, val, level);
|
||||||
}
|
}
|
||||||
// If it's an object, just print doc for that object (no deep recursion)
|
// If it's an object, just print doc for that object (no deep recursion)
|
||||||
else if (val && typeof val === 'object') {
|
else if (val && typeof val == 'object') {
|
||||||
writeSubObject(lines, obj, prop, val, level);
|
writeSubObject(lines, obj, prop, val, level);
|
||||||
}
|
}
|
||||||
// Otherwise, it's a primitive or something else
|
// Otherwise, it's a primitive or something else
|
||||||
@@ -192,8 +192,8 @@ function writeProperty(lines, parentObj, prop, desc, level) {
|
|||||||
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>accessor</sub>';
|
var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>accessor</sub>';
|
||||||
lines.push(heading + '\n');
|
lines.push(heading + '\n');
|
||||||
|
|
||||||
var hasGetter = typeof desc.get === 'function';
|
var hasGetter = typeof desc.get == 'function';
|
||||||
var hasSetter = typeof desc.set === 'function';
|
var hasSetter = typeof desc.set == 'function';
|
||||||
|
|
||||||
if (hasGetter && !hasSetter) {
|
if (hasGetter && !hasSetter) {
|
||||||
lines.push('(read only)\n');
|
lines.push('(read only)\n');
|
||||||
|
|||||||
@@ -66,24 +66,24 @@ Angles in degrees or radians must first be converted prior to making a quaternio
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.transform[cell.DOC].parent = `Get or set the transform's parent. If set, this transform becomes a child of
|
prosperon.c_types.transform[cell.DOC].parent = `Get or set the transform's parent. If set, this transform becomes a child of
|
||||||
the parent (re-parenting). Must be another transform object or undefined.
|
the parent (re-parenting). Must be another transform object or null.
|
||||||
|
|
||||||
:param value: (when setting) Another transform or undefined.
|
:param value: (when setting) Another transform or null.
|
||||||
:return: The current parent transform (when getting), or None (when setting).
|
:return: The current parent transform (when getting), or None (when setting).
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.transform[cell.DOC].change_hook = `A user-supplied function that's called whenever the transform's local matrix changes.
|
prosperon.c_types.transform[cell.DOC].change_hook = `A user-supplied function that's called whenever the transform's local matrix changes.
|
||||||
If undefined, no hook is called.
|
If null, no hook is called.
|
||||||
|
|
||||||
:param value: (when setting) A function.
|
:param value: (when setting) A function.
|
||||||
:return: The current function or undefined.
|
:return: The current function or null.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.transform[cell.DOC].trs = `Set the transform's position, rotation, and scale in one call.
|
prosperon.c_types.transform[cell.DOC].trs = `Set the transform's position, rotation, and scale in one call.
|
||||||
|
|
||||||
:param pos: [x,y,z] for position, or undefined to keep existing.
|
:param pos: [x,y,z] for position, or null to keep existing.
|
||||||
:param quat: [qx,qy,qz,qw] for rotation, or undefined.
|
:param quat: [qx,qy,qz,qw] for rotation, or null.
|
||||||
:param scale: [sx,sy,sz] for scale, or undefined.
|
:param scale: [sx,sy,sz] for scale, or null.
|
||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ prosperon.c_types.datastream[cell.DOC].framerate = `Return the framerate (FPS) o
|
|||||||
prosperon.c_types.datastream[cell.DOC].callback = `A function to call whenever a new frame is decoded. If not set, no callback is invoked.
|
prosperon.c_types.datastream[cell.DOC].callback = `A function to call whenever a new frame is decoded. If not set, no callback is invoked.
|
||||||
|
|
||||||
:param fn: (when setting) A function that receives (surface).
|
:param fn: (when setting) A function that receives (surface).
|
||||||
:return: The existing function or undefined if none.
|
:return: The existing function or null if none.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
@@ -325,10 +325,10 @@ prosperon.c_types.SDL_Camera[cell.DOC] = {}
|
|||||||
prosperon.c_types.SDL_Camera[cell.DOC][cell.DOC] = `A handle to a physical camera device. Freed when references drop or camera is closed.
|
prosperon.c_types.SDL_Camera[cell.DOC][cell.DOC] = `A handle to a physical camera device. Freed when references drop or camera is closed.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Camera[cell.DOC].frame = `Acquire the latest camera frame (as an SDL_Surface). Returns undefined if no
|
prosperon.c_types.SDL_Camera[cell.DOC].frame = `Acquire the latest camera frame (as an SDL_Surface). Returns null if no
|
||||||
new frame is available yet. Throws on error.
|
new frame is available yet. Throws on error.
|
||||||
|
|
||||||
:return: SDL_Surface or undefined.
|
:return: SDL_Surface or null.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Camera[cell.DOC].release_frame = `Release a frame surface previously acquired via camera.frame(). Must be
|
prosperon.c_types.SDL_Camera[cell.DOC].release_frame = `Release a frame surface previously acquired via camera.frame(). Must be
|
||||||
@@ -376,9 +376,9 @@ prosperon.c_types.SDL_Window[cell.DOC].keyboard_shown = `Return whether the on-s
|
|||||||
:return: True if shown, false otherwise.
|
:return: True if shown, false otherwise.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Window[cell.DOC].theme = `Currently returns undefined. Placeholder for retrieving OS window theme info.
|
prosperon.c_types.SDL_Window[cell.DOC].theme = `Currently returns null. Placeholder for retrieving OS window theme info.
|
||||||
|
|
||||||
:return: undefined
|
:return: null
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Window[cell.DOC].safe_area = `Return a rect describing any OS-specific "safe" region for UI, e.g. on iPhone with a notch.
|
prosperon.c_types.SDL_Window[cell.DOC].safe_area = `Return a rect describing any OS-specific "safe" region for UI, e.g. on iPhone with a notch.
|
||||||
@@ -502,7 +502,7 @@ prosperon.c_types.SDL_Renderer[cell.DOC].tile = `Tile a texture repeatedly withi
|
|||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Renderer[cell.DOC].get_image = `Read back the rendered pixels into a new SDL_Surface. If rect is undefined, capture entire output.
|
prosperon.c_types.SDL_Renderer[cell.DOC].get_image = `Read back the rendered pixels into a new SDL_Surface. If rect is null, capture entire output.
|
||||||
|
|
||||||
:param rect: Optional {x,y,w,h}.
|
:param rect: Optional {x,y,w,h}.
|
||||||
:return: An SDL_Surface with the requested region's pixels.
|
:return: An SDL_Surface with the requested region's pixels.
|
||||||
@@ -518,7 +518,7 @@ prosperon.c_types.SDL_Renderer[cell.DOC].fasttext = `Draw debug text using an in
|
|||||||
|
|
||||||
prosperon.c_types.SDL_Renderer[cell.DOC].geometry = `Render custom geometry from a mesh object {pos, uv, color, indices, count} with an optional texture.
|
prosperon.c_types.SDL_Renderer[cell.DOC].geometry = `Render custom geometry from a mesh object {pos, uv, color, indices, count} with an optional texture.
|
||||||
|
|
||||||
:param texture: The SDL_Texture or undefined.
|
:param texture: The SDL_Texture or null.
|
||||||
:param meshObject: The geometry data with typed arrays.
|
:param meshObject: The geometry data with typed arrays.
|
||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
@@ -536,15 +536,15 @@ For example, (320, 240) can auto-scale up to the window resolution.
|
|||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Renderer[cell.DOC].viewport = `Set the clipping viewport for rendering. Pass undefined to use the full render target.
|
prosperon.c_types.SDL_Renderer[cell.DOC].viewport = `Set the clipping viewport for rendering. Pass null to use the full render target.
|
||||||
|
|
||||||
:param rect: {x, y, w, h}, or undefined.
|
:param rect: {x, y, w, h}, or null.
|
||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Renderer[cell.DOC].clip = `Set or clear the clipping rectangle for drawing. Pass undefined to clear.
|
prosperon.c_types.SDL_Renderer[cell.DOC].clip = `Set or clear the clipping rectangle for drawing. Pass null to clear.
|
||||||
|
|
||||||
:param rect: {x, y, w, h} or undefined.
|
:param rect: {x, y, w, h} or null.
|
||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -579,9 +579,9 @@ prosperon.c_types.SDL_Renderer[cell.DOC].screen2world = `Convert a screen coordi
|
|||||||
:return: [wx, wy] in world space
|
:return: [wx, wy] in world space
|
||||||
`;
|
`;
|
||||||
|
|
||||||
prosperon.c_types.SDL_Renderer[cell.DOC].target = `Set or clear the current render target texture. Pass undefined to reset to the default/window.
|
prosperon.c_types.SDL_Renderer[cell.DOC].target = `Set or clear the current render target texture. Pass null to reset to the default/window.
|
||||||
|
|
||||||
:param texture: An SDL_Texture or undefined
|
:param texture: An SDL_Texture or null
|
||||||
:return: None
|
:return: None
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
(function engine() {
|
(function engine() {
|
||||||
globalThis.cell = prosperon
|
globalThis.cell = prosperon
|
||||||
cell.DOC = cell.hidden.DOCSYM
|
cell.DOC = Symbol()
|
||||||
var ACTORDATA = cell.hidden.ACTORSYM
|
var ACTORDATA = cell.hidden.actorsym
|
||||||
ACTORDATA = '__ACTORDATA__' // TODO: implement the actual actorsym
|
|
||||||
var SYSYM = '__SYSTEM__'
|
var SYSYM = '__SYSTEM__'
|
||||||
|
|
||||||
var ENETSERVICE = 0.1
|
var ENETSERVICE = 0.1
|
||||||
@@ -44,7 +43,7 @@ var console_mod = cell.hidden.console
|
|||||||
var logs = {}
|
var logs = {}
|
||||||
logs.console = function(msg)
|
logs.console = function(msg)
|
||||||
{
|
{
|
||||||
var caller = caller_data(4)
|
var caller = caller_data(2)
|
||||||
console_mod.print(console_rec(caller.line, caller.file, msg))
|
console_mod.print(console_rec(caller.line, caller.file, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ function noop() {}
|
|||||||
globalThis.log = new Proxy(logs, {
|
globalThis.log = new Proxy(logs, {
|
||||||
get(target,prop,receiver) {
|
get(target,prop,receiver) {
|
||||||
if (target[prop])
|
if (target[prop])
|
||||||
return (...args) => args.forEach(arg => target[prop](arg))
|
return (...args) => target[prop](args.join(' '))
|
||||||
|
|
||||||
return noop
|
return noop
|
||||||
}
|
}
|
||||||
@@ -83,24 +82,37 @@ var use_dyn = hidden.use_dyn
|
|||||||
var enet = hidden.enet
|
var enet = hidden.enet
|
||||||
var nota = hidden.nota
|
var nota = hidden.nota
|
||||||
|
|
||||||
|
// Wota decode timing tracking
|
||||||
|
var wota_decode_times = []
|
||||||
|
var last_wota_flush = 0
|
||||||
|
|
||||||
// Strip hidden from cell so nothing else can access it
|
// Strip hidden from cell so nothing else can access it
|
||||||
delete cell.hidden
|
delete cell.hidden
|
||||||
|
|
||||||
var os = use_embed('os')
|
|
||||||
|
|
||||||
function disrupt(err)
|
function disrupt(err)
|
||||||
{
|
{
|
||||||
if (overling) {
|
if (overling) {
|
||||||
var reason = (err instanceof Error) ? err.stack : err
|
if (err) {
|
||||||
report_to_overling({type:'disrupt', reason})
|
// with an err, this is a forceful disrupt
|
||||||
|
var reason = (err instanceof Error) ? err.stack : err
|
||||||
|
report_to_overling({type:'disrupt', reason})
|
||||||
|
} else
|
||||||
|
report_to_overling({type:'stop'})
|
||||||
}
|
}
|
||||||
|
|
||||||
log.error(err)
|
|
||||||
|
|
||||||
|
for (var id of underlings)
|
||||||
|
$_.stop(create_actor({id}))
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
log.console(err);
|
||||||
|
if (err.stack)
|
||||||
|
log.console(err.stack)
|
||||||
|
}
|
||||||
|
|
||||||
actor_mod.disrupt()
|
actor_mod.disrupt()
|
||||||
}
|
}
|
||||||
|
|
||||||
os.on(disrupt)
|
actor_mod.on_exception(disrupt)
|
||||||
|
|
||||||
var js = use_embed('js')
|
var js = use_embed('js')
|
||||||
var io = use_embed('io')
|
var io = use_embed('io')
|
||||||
@@ -237,13 +249,18 @@ globalThis.use = function use(file, ...args) {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalThis.json = use('json')
|
||||||
|
var time = use('time')
|
||||||
|
var st_now = time.number()
|
||||||
|
|
||||||
var shop = use('shop')
|
var shop = use('shop')
|
||||||
var config = shop.load_config()
|
var config = shop.load_config()
|
||||||
var default_config = {
|
var default_config = {
|
||||||
ar_timer: 60,
|
ar_timer: 60,
|
||||||
actor_memory:0,
|
actor_memory:0,
|
||||||
net_service:0.1,
|
net_service:0.1,
|
||||||
reply_timeout:60
|
reply_timeout:60,
|
||||||
|
main: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
config.system ??= {}
|
config.system ??= {}
|
||||||
@@ -251,16 +268,34 @@ config.system.__proto__ = default_config
|
|||||||
|
|
||||||
ENETSERVICE = config.system.net_service
|
ENETSERVICE = config.system.net_service
|
||||||
REPLYTIMEOUT = config.system.reply_timeout
|
REPLYTIMEOUT = config.system.reply_timeout
|
||||||
|
|
||||||
globalThis.json = use('json')
|
|
||||||
globalThis.text = use('text')
|
globalThis.text = use('text')
|
||||||
var time = use('time')
|
|
||||||
|
// Load actor-specific configuration
|
||||||
|
function load_actor_config(program) {
|
||||||
|
// Extract actor name from program path
|
||||||
|
// e.g., "prosperon/_sdl_video" or "extramath/spline"
|
||||||
|
var actor_name = program
|
||||||
|
if (program.includes('/')) {
|
||||||
|
actor_name = program
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.actors && config.actors[actor_name]) {
|
||||||
|
for (var key in config.actors[actor_name])
|
||||||
|
cell.args[key] = config.actors[actor_name][key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var blob = use('blob')
|
var blob = use('blob')
|
||||||
|
|
||||||
|
var blob_stone = blob.prototype.stone
|
||||||
|
var blob_stonep = blob.prototype.stonep;
|
||||||
|
delete blob.prototype.stone;
|
||||||
|
delete blob.prototype.stonep;
|
||||||
|
|
||||||
function deepFreeze(object) {
|
function deepFreeze(object) {
|
||||||
if (object instanceof blob)
|
if (object instanceof blob)
|
||||||
object.stone()
|
blob_stone.call(object);
|
||||||
|
|
||||||
// Retrieve the property names defined on object
|
// Retrieve the property names defined on object
|
||||||
var propNames = Object.keys(object);
|
var propNames = Object.keys(object);
|
||||||
@@ -269,7 +304,7 @@ function deepFreeze(object) {
|
|||||||
for (var name of propNames) {
|
for (var name of propNames) {
|
||||||
var value = object[name];
|
var value = object[name];
|
||||||
|
|
||||||
if ((value && typeof value === "object") || typeof value === "function") {
|
if ((value && typeof value == "object") || typeof value == "function") {
|
||||||
deepFreeze(value);
|
deepFreeze(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,14 +315,9 @@ function deepFreeze(object) {
|
|||||||
globalThis.stone = deepFreeze
|
globalThis.stone = deepFreeze
|
||||||
stone.p = function(object)
|
stone.p = function(object)
|
||||||
{
|
{
|
||||||
if (object instanceof blob) {
|
if (object instanceof blob)
|
||||||
try {
|
return blob_stonep.call(object)
|
||||||
object.read_logical(0)
|
|
||||||
return true
|
|
||||||
} catch(e) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Object.isFrozen(object)
|
return Object.isFrozen(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,13 +344,16 @@ stone.p = function(object)
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var util = use('util')
|
function guid(bits = 256)
|
||||||
var math = use('math')
|
{
|
||||||
var crypto = use('crypto')
|
var guid = new blob(bits, hidden.randi)
|
||||||
|
stone(guid)
|
||||||
|
return text(guid,'h')
|
||||||
|
}
|
||||||
|
|
||||||
var HEADER = Symbol()
|
var HEADER = Symbol()
|
||||||
|
|
||||||
function create_actor(desc = {id:util.guid()}) {
|
function create_actor(desc = {id:guid()}) {
|
||||||
var actor = {}
|
var actor = {}
|
||||||
actor[ACTORDATA] = desc
|
actor[ACTORDATA] = desc
|
||||||
return actor
|
return actor
|
||||||
@@ -328,21 +361,24 @@ function create_actor(desc = {id:util.guid()}) {
|
|||||||
|
|
||||||
var $_ = create_actor()
|
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[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) { return os.now() }
|
$_.clock = function(fn) {
|
||||||
|
actor_mod.clock(_ => {
|
||||||
|
fn(time.number())
|
||||||
|
send_messages()
|
||||||
|
})
|
||||||
|
}
|
||||||
$_.clock[cell.DOC] = "takes a function input value that will eventually be called with the current time in number form."
|
$_.clock[cell.DOC] = "takes a function input value that will eventually be called with the current time in number form."
|
||||||
|
|
||||||
var underlings = new Set() // this is more like "all actors that are notified when we die"
|
var underlings = new Set() // this is more like "all actors that are notified when we die"
|
||||||
var overling = undefined
|
var overling = null
|
||||||
var root = undefined
|
var root = null
|
||||||
|
|
||||||
// Don't make $_ global - it should only be available to actor scripts
|
var receive_fn = null
|
||||||
|
|
||||||
var receive_fn = undefined
|
|
||||||
var greeters = {} // Router functions for when messages are received for a specific actor
|
var greeters = {} // Router functions for when messages are received for a specific actor
|
||||||
|
|
||||||
globalThis.is_actor = function is_actor(actor) {
|
globalThis.is_actor = function is_actor(actor) {
|
||||||
@@ -389,9 +425,9 @@ $_.connection[cell.DOC] = "The connection function takes a callback function, an
|
|||||||
|
|
||||||
var peers = {}
|
var peers = {}
|
||||||
var id_address = {}
|
var id_address = {}
|
||||||
var peer_queue = new WeakMap()
|
var peer_queue = {}
|
||||||
var portal = undefined
|
var portal = null
|
||||||
var portal_fn = undefined
|
var portal_fn = null
|
||||||
|
|
||||||
$_.portal = function(fn, port) {
|
$_.portal = function(fn, port) {
|
||||||
if (portal) throw new Error(`Already started a portal listening on ${portal.port}`)
|
if (portal) throw new Error(`Already started a portal listening on ${portal.port}`)
|
||||||
@@ -416,7 +452,7 @@ function handle_host(e) {
|
|||||||
break
|
break
|
||||||
case "disconnect":
|
case "disconnect":
|
||||||
peer_queue.delete(e.peer)
|
peer_queue.delete(e.peer)
|
||||||
for (var id in peers) if (peers[id] === e.peer) delete peers[id]
|
for (var id in peers) if (peers[id] == e.peer) delete peers[id]
|
||||||
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
|
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
|
||||||
break
|
break
|
||||||
case "receive":
|
case "receive":
|
||||||
@@ -426,7 +462,7 @@ function handle_host(e) {
|
|||||||
data.replycc[ACTORDATA].port = e.peer.port
|
data.replycc[ACTORDATA].port = e.peer.port
|
||||||
}
|
}
|
||||||
function populate_actor_addresses(obj) {
|
function populate_actor_addresses(obj) {
|
||||||
if (typeof obj !== 'object' || obj === null) return
|
if (typeof obj != 'object' || obj == null) return
|
||||||
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
|
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
|
||||||
obj[ACTORDATA].address = e.peer.address
|
obj[ACTORDATA].address = e.peer.address
|
||||||
obj[ACTORDATA].port = e.peer.port
|
obj[ACTORDATA].port = e.peer.port
|
||||||
@@ -457,9 +493,9 @@ $_.receiver[cell.DOC] = "registers a function that will receive all messages..."
|
|||||||
|
|
||||||
$_.start = function start(cb, program, ...args) {
|
$_.start = function start(cb, program, ...args) {
|
||||||
if (!program) return
|
if (!program) return
|
||||||
var id = util.guid()
|
var id = guid()
|
||||||
|
|
||||||
if (args.length === 1 && Array.isArray(args[0]))
|
if (args.length == 1 && Array.isArray(args[0]))
|
||||||
args = args[0]
|
args = args[0]
|
||||||
|
|
||||||
var startup = {
|
var startup = {
|
||||||
@@ -475,15 +511,15 @@ $_.start = function start(cb, program, ...args) {
|
|||||||
|
|
||||||
$_.stop = function stop(actor) {
|
$_.stop = function stop(actor) {
|
||||||
if (!actor) {
|
if (!actor) {
|
||||||
destroyself()
|
need_stop = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!is_actor(actor))
|
if (!is_actor(actor))
|
||||||
throw new Error('Can only call stop on an actor.')
|
throw new Error('Can only call stop on an actor.')
|
||||||
if (!underlings.has(actor[ACTORDATA].id))
|
if (!underlings.has(actor[ACTORDATA].id))
|
||||||
throw new Error('Can only call stop on an underling or self.')
|
throw new Error('Can only call stop on an underling or self.')
|
||||||
|
|
||||||
sys_msg(actor, {kind:"stop"})
|
sys_msg(actor, "stop")
|
||||||
}
|
}
|
||||||
$_.stop[cell.DOC] = "The stop function stops an underling."
|
$_.stop[cell.DOC] = "The stop function stops an underling."
|
||||||
|
|
||||||
@@ -491,7 +527,12 @@ $_.unneeded = function unneeded(fn, seconds) {
|
|||||||
actor_mod.unneeded(fn, seconds)
|
actor_mod.unneeded(fn, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
$_.delay = function delay(fn, seconds) {
|
$_.delay = function delay(fn, seconds = 0) {
|
||||||
|
if (seconds <= 0) {
|
||||||
|
$_.clock(fn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
function delay_turn() {
|
function delay_turn() {
|
||||||
fn()
|
fn()
|
||||||
send_messages()
|
send_messages()
|
||||||
@@ -503,9 +544,9 @@ $_.delay[cell.DOC] = "used to schedule the invocation of a function..."
|
|||||||
|
|
||||||
var couplings = new Set()
|
var couplings = new Set()
|
||||||
$_.couple = function couple(actor) {
|
$_.couple = function couple(actor) {
|
||||||
if (actor === $_) return // can't couple to self
|
if (actor == $_) return // can't couple to self
|
||||||
couplings.add(actor[ACTORDATA].id)
|
couplings.add(actor[ACTORDATA].id)
|
||||||
sys_msg(actor, {kind:'couple'})
|
sys_msg(actor, {kind:'couple', from: $_})
|
||||||
log.system(`coupled to ${actor}`)
|
log.system(`coupled to ${actor}`)
|
||||||
}
|
}
|
||||||
$_.couple[cell.DOC] = "causes this actor to stop when another actor stops."
|
$_.couple[cell.DOC] = "causes this actor to stop when another actor stops."
|
||||||
@@ -529,17 +570,19 @@ function actor_send(actor, message) {
|
|||||||
|
|
||||||
if (!is_actor(actor) && !is_actor(actor.replycc)) throw new Error(`Must send to an actor object. Attempted send to ${json.encode(actor)}`)
|
if (!is_actor(actor) && !is_actor(actor.replycc)) throw new Error(`Must send to an actor object. Attempted send to ${json.encode(actor)}`)
|
||||||
|
|
||||||
if (typeof message !== 'object') throw new Error('Must send an object record.')
|
if (typeof message != 'object') throw new Error('Must send an object record.')
|
||||||
|
|
||||||
// message to self
|
// message to self
|
||||||
if (actor[ACTORDATA].id === cell.id) {
|
if (actor[ACTORDATA].id == cell.id) {
|
||||||
if (receive_fn) receive_fn(message.data)
|
if (receive_fn) receive_fn(message.data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// message to actor in same flock
|
// message to actor in same flock
|
||||||
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
|
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
|
||||||
actor_mod.mailbox_push(actor[ACTORDATA].id, message)
|
var wota_blob = wota.encode(message)
|
||||||
|
// log.console(`sending wota blob of ${wota_blob.length/8} bytes`)
|
||||||
|
actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,26 +607,31 @@ function actor_send(actor, message) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.system(`Unable to send message to actor ${json.encode(actor)}`)
|
log.system(`Unable to send message to actor ${json.encode(actor[ACTORDATA])}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Holds all messages queued during the current turn.
|
// Holds all messages queued during the current turn.
|
||||||
var message_queue = []
|
var message_queue = []
|
||||||
|
|
||||||
|
var need_stop = false
|
||||||
|
|
||||||
function send_messages() {
|
function send_messages() {
|
||||||
for (var msg of message_queue)
|
for (var msg of message_queue)
|
||||||
actor_send(msg.actor,msg.send)
|
actor_send(msg.actor,msg.send)
|
||||||
|
|
||||||
message_queue.length = 0
|
message_queue.length = 0
|
||||||
|
|
||||||
|
if (need_stop)
|
||||||
|
disrupt()
|
||||||
}
|
}
|
||||||
|
|
||||||
var replies = {}
|
var replies = {}
|
||||||
|
|
||||||
globalThis.send = function send(actor, message, reply) {
|
globalThis.send = function send(actor, message, reply) {
|
||||||
if (typeof actor !== 'object')
|
if (typeof actor != 'object')
|
||||||
throw new Error('Must send to an actor object. Provided: ' + actor);
|
throw new Error('Must send to an actor object. Provided: ' + actor);
|
||||||
|
|
||||||
if (typeof message !== 'object')
|
if (typeof message != 'object')
|
||||||
throw new Error('Message must be an object')
|
throw new Error('Message must be an object')
|
||||||
var send = {type:"user", data: message}
|
var send = {type:"user", data: message}
|
||||||
|
|
||||||
@@ -597,11 +645,11 @@ globalThis.send = function send(actor, message, reply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reply) {
|
if (reply) {
|
||||||
var id = util.guid()
|
var id = guid()
|
||||||
replies[id] = reply
|
replies[id] = reply
|
||||||
$_.delay(_ => {
|
$_.delay(_ => {
|
||||||
if (replies[id]) {
|
if (replies[id]) {
|
||||||
replies[id](undefined, "timeout")
|
replies[id](null, "timeout")
|
||||||
delete replies[id]
|
delete replies[id]
|
||||||
}
|
}
|
||||||
}, REPLYTIMEOUT)
|
}, REPLYTIMEOUT)
|
||||||
@@ -615,7 +663,7 @@ globalThis.send = function send(actor, message, reply) {
|
|||||||
|
|
||||||
stone(send)
|
stone(send)
|
||||||
|
|
||||||
if (!cell.args.id) cell.id = util.guid()
|
if (!cell.args.id) cell.id = guid()
|
||||||
else cell.id = cell.args.id
|
else cell.id = cell.args.id
|
||||||
|
|
||||||
$_[ACTORDATA].id = cell.id
|
$_[ACTORDATA].id = cell.id
|
||||||
@@ -623,11 +671,20 @@ $_[ACTORDATA].id = cell.id
|
|||||||
// Actor's timeslice for processing a single message
|
// Actor's timeslice for processing a single message
|
||||||
function turn(msg)
|
function turn(msg)
|
||||||
{
|
{
|
||||||
handle_message(msg)
|
var mes = wota.decode(msg)
|
||||||
|
handle_message(mes)
|
||||||
send_messages()
|
send_messages()
|
||||||
}
|
}
|
||||||
|
|
||||||
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system)
|
load_actor_config(cell.args.program)
|
||||||
|
|
||||||
|
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer)
|
||||||
|
|
||||||
|
if (config.system.actor_memory)
|
||||||
|
js.mem_limit(config.system.actor_memory)
|
||||||
|
|
||||||
|
if (config.system.stack_max)
|
||||||
|
js.max_stacksize(config.system.stack_max);
|
||||||
|
|
||||||
overling = cell.args.overling
|
overling = cell.args.overling
|
||||||
root = cell.args.root
|
root = cell.args.root
|
||||||
@@ -642,28 +699,19 @@ if (overling) {
|
|||||||
// sys messages are always dispatched immediately
|
// sys messages are always dispatched immediately
|
||||||
function sys_msg(actor, msg)
|
function sys_msg(actor, msg)
|
||||||
{
|
{
|
||||||
actor_send(actor, {[SYSYM]:msg, from:$_})
|
actor_send(actor, {[SYSYM]:msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
// messages sent to here get put into the cb provided to start
|
// messages sent to here get put into the cb provided to start
|
||||||
function report_to_overling(msg)
|
function report_to_overling(msg)
|
||||||
{
|
{
|
||||||
if (!overling) return
|
if (!overling) return
|
||||||
sys_msg(overling, {kind:'underling', message:msg})
|
sys_msg(overling, {kind:'underling', message:msg, from: $_})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cell.args.program)
|
if (!cell.args.program)
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
|
|
||||||
function destroyself() {
|
|
||||||
for (var id of underlings)
|
|
||||||
$_.stop(create_actor({id}))
|
|
||||||
|
|
||||||
if (overling) report_to_overling({type:'stop'})
|
|
||||||
|
|
||||||
actor_mod.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle_actor_disconnect(id) {
|
function handle_actor_disconnect(id) {
|
||||||
var greeter = greeters[id]
|
var greeter = greeters[id]
|
||||||
if (greeter) {
|
if (greeter) {
|
||||||
@@ -674,18 +722,19 @@ function handle_actor_disconnect(id) {
|
|||||||
if (couplings.has(id)) disrupt("coupled actor died") // couplings now disrupts instead of stop
|
if (couplings.has(id)) disrupt("coupled actor died") // couplings now disrupts instead of stop
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_sysym(msg, from)
|
function handle_sysym(msg)
|
||||||
{
|
{
|
||||||
|
|
||||||
switch(msg.kind) {
|
switch(msg.kind) {
|
||||||
case 'stop':
|
case 'stop':
|
||||||
if (from[ACTORDATA].id !== overling[ACTORDATA].id)
|
disrupt("got stop message")
|
||||||
log.error(`Got a message from a random actor ${msg.id} to stop`)
|
|
||||||
else
|
|
||||||
disrupt("got stop message")
|
|
||||||
break
|
break
|
||||||
case 'underling':
|
case 'underling':
|
||||||
|
var from = msg.from
|
||||||
var greeter = greeters[from[ACTORDATA].id]
|
var greeter = greeters[from[ACTORDATA].id]
|
||||||
if (greeter) greeter(msg.message)
|
if (greeter) greeter(msg.message)
|
||||||
|
if (msg.message.type == 'disrupt')
|
||||||
|
underlings.delete(from[ACTORDATA].id)
|
||||||
break
|
break
|
||||||
case 'contact':
|
case 'contact':
|
||||||
if (portal_fn) {
|
if (portal_fn) {
|
||||||
@@ -696,6 +745,7 @@ function handle_sysym(msg, from)
|
|||||||
} else throw new Error('Got a contact message, but no portal is established.')
|
} else throw new Error('Got a contact message, but no portal is established.')
|
||||||
break
|
break
|
||||||
case 'couple': // from must be notified when we die
|
case 'couple': // from must be notified when we die
|
||||||
|
var from = msg.from
|
||||||
underlings.add(from[ACTORDATA].id)
|
underlings.add(from[ACTORDATA].id)
|
||||||
log.system(`actor ${from} is coupled to me`)
|
log.system(`actor ${from} is coupled to me`)
|
||||||
break
|
break
|
||||||
@@ -714,7 +764,7 @@ function handle_message(msg) {
|
|||||||
Object.defineProperty(letter, HEADER, {
|
Object.defineProperty(letter, HEADER, {
|
||||||
value: msg, enumerable: false
|
value: msg, enumerable: false
|
||||||
})
|
})
|
||||||
Object.defineProperty(letter, ACTORDATA, { // this is so is_actor === true
|
Object.defineProperty(letter, ACTORDATA, { // this is so is_actor == true
|
||||||
value: { reply: msg.reply }, enumerable: false
|
value: { reply: msg.reply }, enumerable: false
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -751,15 +801,22 @@ var progPath = cell.args.program
|
|||||||
if (io.exists(progPath + ACTOR_EXT) && !io.is_directory(progPath + ACTOR_EXT)) {
|
if (io.exists(progPath + ACTOR_EXT) && !io.is_directory(progPath + ACTOR_EXT)) {
|
||||||
prog = progPath + ACTOR_EXT
|
prog = progPath + ACTOR_EXT
|
||||||
} else if (io.exists(progPath) && io.is_directory(progPath)) {
|
} else if (io.exists(progPath) && io.is_directory(progPath)) {
|
||||||
var mainPath = progPath + '/main' + ACTOR_EXT
|
// First check for folder's name as a file
|
||||||
if (io.exists(mainPath) && !io.is_directory(mainPath)) {
|
var folderName = progPath.split('/').pop()
|
||||||
prog = mainPath
|
var folderNamePath = progPath + '/' + folderName + ACTOR_EXT
|
||||||
|
if (io.exists(folderNamePath) && !io.is_directory(folderNamePath)) {
|
||||||
|
prog = folderNamePath
|
||||||
|
} else {
|
||||||
|
// Fall back to main.ce
|
||||||
|
var mainPath = progPath + '/main' + ACTOR_EXT
|
||||||
|
if (io.exists(mainPath) && !io.is_directory(mainPath)) {
|
||||||
|
prog = mainPath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prog)
|
if (!prog)
|
||||||
throw new Error(cell.args.program + " not found.");
|
throw new Error(cell.args.program + " not found.");
|
||||||
|
|
||||||
|
|
||||||
var progDir = io.realdir(prog) + "/" + prog.substring(0, prog.lastIndexOf('/'))
|
var progDir = io.realdir(prog) + "/" + prog.substring(0, prog.lastIndexOf('/'))
|
||||||
|
|
||||||
@@ -769,12 +826,16 @@ var progContent = io.slurp(prog)
|
|||||||
|
|
||||||
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
|
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
|
||||||
|
|
||||||
var val = js.eval(cell.args.program, prog_script)($_, cell.args.arg)
|
// queue up its first turn instead of run immediately
|
||||||
if (val)
|
|
||||||
throw new Error('Program must not return anything');
|
var startfn = js.eval(cell.args.program, prog_script);
|
||||||
|
$_.clock(_ => {
|
||||||
|
var val = startfn($_, cell.args.arg);
|
||||||
|
|
||||||
log.console("WAYDOWN")
|
if (val)
|
||||||
|
throw new Error('Program must not return anything');
|
||||||
send_messages()
|
})
|
||||||
|
|
||||||
|
log.console(`startup took ${time.number()-st_now}`)
|
||||||
|
|
||||||
})()
|
})()
|
||||||
@@ -19,7 +19,7 @@ var parsed = shop.parse_locator(locator)
|
|||||||
|
|
||||||
// If no version specified, append @head
|
// If no version specified, append @head
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
if (locator.indexOf('@') === -1) {
|
if (locator.indexOf('@') == -1) {
|
||||||
locator = locator + '@head'
|
locator = locator + '@head'
|
||||||
parsed = shop.parse_locator(locator)
|
parsed = shop.parse_locator(locator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ if (io.exists(cell_man)) {
|
|||||||
log.console(" vendor Copy all dependencies locally")
|
log.console(" vendor Copy all dependencies locally")
|
||||||
log.console(" build Compile all modules to bytecode")
|
log.console(" build Compile all modules to bytecode")
|
||||||
log.console(" patch Create a patch for a module")
|
log.console(" patch Create a patch for a module")
|
||||||
|
log.console(" config Manage system and actor configurations")
|
||||||
log.console(" help Show this help message")
|
log.console(" help Show this help message")
|
||||||
log.console("")
|
log.console("")
|
||||||
log.console("Run 'cell help <command>' for more information on a command.")
|
log.console("Run 'cell help <command>' for more information on a command.")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ This function enqueues an HTTP GET request for the specified URL. It supports bo
|
|||||||
:param options: Either a callback function or an object with optional properties:
|
:param options: Either a callback function or an object with optional properties:
|
||||||
- 'callback': A function invoked upon request completion, receiving an object with 'data' (string or null) and 'error' (string or null) properties.
|
- 'callback': A function invoked upon request completion, receiving an object with 'data' (string or null) and 'error' (string or null) properties.
|
||||||
- 'on_data': A function invoked for each chunk of streaming data, receiving a string chunk as its argument. If supplied, 'callback.data' will be null.
|
- 'on_data': A function invoked for each chunk of streaming data, receiving a string chunk as its argument. If supplied, 'callback.data' will be null.
|
||||||
:return: undefined
|
:return: null
|
||||||
:throws:
|
:throws:
|
||||||
- An error if the URL is not a string or is invalid.
|
- An error if the URL is not a string or is invalid.
|
||||||
- An error if the options argument is neither a function nor an object.
|
- An error if the options argument is neither a function nor an object.
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ Throw on error.
|
|||||||
:return: None
|
:return: None
|
||||||
`
|
`
|
||||||
|
|
||||||
io.mount[cell.DOC] = `Mount a directory or archive at the specified mount point. An undefined mount
|
io.mount[cell.DOC] = `Mount a directory or archive at the specified mount point. An null mount
|
||||||
point mounts to '/'. Throw on error.
|
point mounts to '/'. Throw on error.
|
||||||
|
|
||||||
:param archiveOrDir: The directory or archive to mount.
|
:param archiveOrDir: The directory or archive to mount.
|
||||||
:param mountPoint: The path at which to mount. If omitted or undefined, '/' is used.
|
:param mountPoint: The path at which to mount. If omitted or null, '/' is used.
|
||||||
:return: None
|
:return: None
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -145,10 +145,10 @@ operations. Throw on error.
|
|||||||
`
|
`
|
||||||
|
|
||||||
io.realdir[cell.DOC] = `Return the actual, real directory (on the host filesystem) that contains the given
|
io.realdir[cell.DOC] = `Return the actual, real directory (on the host filesystem) that contains the given
|
||||||
file path. Return undefined if not found.
|
file path. Return null if not found.
|
||||||
|
|
||||||
:param path: The file path whose real directory is requested.
|
:param path: The file path whose real directory is requested.
|
||||||
:return: A string with the real directory path, or undefined.
|
:return: A string with the real directory path, or null.
|
||||||
`
|
`
|
||||||
|
|
||||||
io.searchpath[cell.DOC] = `Return an array of all directories in the current paths.
|
io.searchpath[cell.DOC] = `Return an array of all directories in the current paths.
|
||||||
|
|||||||
124
scripts/jswota.cm
Normal file
124
scripts/jswota.cm
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
var blob = use('blob')
|
||||||
|
var utf8 = use('utf8')
|
||||||
|
|
||||||
|
var INT = new blob(8, false)
|
||||||
|
stone(INT)
|
||||||
|
|
||||||
|
var FP_HEADER = new blob(64)
|
||||||
|
FP_HEADER.write_fit(0,56)
|
||||||
|
FP_HEADER.write_fit(1,8)
|
||||||
|
stone(FP_HEADER)
|
||||||
|
|
||||||
|
var ARRAY = new blob(8)
|
||||||
|
ARRAY.write_fit(2,8)
|
||||||
|
stone(ARRAY)
|
||||||
|
|
||||||
|
var RECORD = new blob(8)
|
||||||
|
RECORD.write_fit(3,8)
|
||||||
|
stone(RECORD)
|
||||||
|
|
||||||
|
var BLOB = new blob(8)
|
||||||
|
BLOB.write_fit(4,8)
|
||||||
|
stone(BLOB)
|
||||||
|
|
||||||
|
var TEXT = new blob(8)
|
||||||
|
TEXT.write_fit(5,8)
|
||||||
|
stone(TEXT)
|
||||||
|
|
||||||
|
var NULL_SYMBOL = new blob(64)
|
||||||
|
NULL_SYMBOL.write_fit(0,56)
|
||||||
|
NULL_SYMBOL.write_fit(7,8)
|
||||||
|
stone(NULL_SYMBOL)
|
||||||
|
|
||||||
|
var FALSE_SYMBOL = new blob(64)
|
||||||
|
FALSE_SYMBOL.write_fit(2,56)
|
||||||
|
FALSE_SYMBOL.write_fit(7,8)
|
||||||
|
stone(FALSE_SYMBOL)
|
||||||
|
|
||||||
|
var TRUE_SYMBOL = new blob(64)
|
||||||
|
TRUE_SYMBOL.write_fit(3,56)
|
||||||
|
TRUE_SYMBOL.write_fit(7,8)
|
||||||
|
stone(TRUE_SYMBOL)
|
||||||
|
|
||||||
|
var PRIVATE_SYMBOL = new blob(64)
|
||||||
|
PRIVATE_SYMBOL.write_fit(8,56)
|
||||||
|
PRIVATE_SYMBOL.write_fit(7,8)
|
||||||
|
stone(PRIVATE_SYMBOL)
|
||||||
|
|
||||||
|
var SYSTEM_SYMBOL = new blob(64)
|
||||||
|
SYSTEM_SYMBOL.write_fit(9,56)
|
||||||
|
SYSTEM_SYMBOL.write_fit(7,8)
|
||||||
|
stone(SYSTEM_SYMBOL)
|
||||||
|
|
||||||
|
var key_cache = {}
|
||||||
|
|
||||||
|
function encode_key(key)
|
||||||
|
{
|
||||||
|
if (key_cache[key])
|
||||||
|
return key_cache[key]
|
||||||
|
|
||||||
|
var encoded_key = utf8.encode(key)
|
||||||
|
var cached_blob = new blob(64 + encoded_key.length)
|
||||||
|
cached_blob.write_fit(utf8.byte_length(key), 56)
|
||||||
|
cached_blob.write_blob(TEXT)
|
||||||
|
cached_blob.write_blob(encoded_key)
|
||||||
|
stone(cached_blob)
|
||||||
|
|
||||||
|
key_cache[key] = cached_blob
|
||||||
|
return cached_blob
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode_val(b, val)
|
||||||
|
{
|
||||||
|
var type = typeof val
|
||||||
|
if (type == 'number') {
|
||||||
|
b.write_blob(FP_HEADER)
|
||||||
|
b.write_number(val)
|
||||||
|
} else if (type == 'string') {
|
||||||
|
b.write_fit(utf8.byte_length(val), 56)
|
||||||
|
b.write_blob(TEXT)
|
||||||
|
b.write_blob(utf8.encode(val))
|
||||||
|
} else if (type == 'boolean') {
|
||||||
|
if (val)
|
||||||
|
b.write_blob(TRUE_SYMBOL)
|
||||||
|
else
|
||||||
|
b.write_blob(FALSE_SYMBOL)
|
||||||
|
} else if (type == 'null') {
|
||||||
|
b.write_blob(NULL_SYMBOL)
|
||||||
|
} else if (type == 'object') {
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
b.write_fit(val.length, 56)
|
||||||
|
b.write_blob(ARRAY)
|
||||||
|
for (var v of val)
|
||||||
|
encode_val(b, v)
|
||||||
|
} else if (val instanceof blob) {
|
||||||
|
b.write_fit(val.length, 56)
|
||||||
|
b.write_blob(BLOB)
|
||||||
|
b.write_blob(val)
|
||||||
|
} else {
|
||||||
|
var keys = Object.keys(val)
|
||||||
|
b.write_fit(keys.length, 56)
|
||||||
|
b.write_blob(RECORD)
|
||||||
|
for (var key of keys) {
|
||||||
|
if (typeof val[key] == 'function') continue
|
||||||
|
b.write_blob(encode_key(key))
|
||||||
|
encode_val(b, val[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode(val)
|
||||||
|
{
|
||||||
|
var b = new blob(8*256)// guess a good length
|
||||||
|
encode_val(b,val)
|
||||||
|
|
||||||
|
return stone(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode(b)
|
||||||
|
{
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return { INT, FP_HEADER, ARRAY, RECORD, BLOB, TEXT, NULL_SYMBOL, FALSE_SYMBOL, TRUE_SYMBOL, PRIVATE_SYMBOL, SYSTEM_SYMBOL, encode, decode }
|
||||||
@@ -16,7 +16,7 @@ if (!config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List dependencies
|
// List dependencies
|
||||||
if (!config.dependencies || Object.keys(config.dependencies).length === 0) {
|
if (!config.dependencies || Object.keys(config.dependencies).length == 0) {
|
||||||
log.console("No modules installed")
|
log.console("No modules installed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
102
scripts/man/config.man
Normal file
102
scripts/man/config.man
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
.TH CELL-CONFIG 1 "2025" "Cell" "Cell Manual"
|
||||||
|
.SH NAME
|
||||||
|
cell config \- manage system and actor configurations
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B cell config
|
||||||
|
.I command
|
||||||
|
[options]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
The
|
||||||
|
.B cell config
|
||||||
|
command manages configuration settings for the Cell system. It provides
|
||||||
|
functionality to view, set, and manage both system-wide and actor-specific
|
||||||
|
configuration values stored in
|
||||||
|
.IR .cell/cell.toml .
|
||||||
|
.SH COMMANDS
|
||||||
|
.TP
|
||||||
|
.B get \fIkey\fR
|
||||||
|
Get a configuration value. Keys use dot notation (e.g., system.ar_timer).
|
||||||
|
.TP
|
||||||
|
.B set \fIkey\fR \fIvalue\fR
|
||||||
|
Set a configuration value. Values are automatically parsed to appropriate types.
|
||||||
|
.TP
|
||||||
|
.B list
|
||||||
|
List all configuration values in a hierarchical format.
|
||||||
|
.TP
|
||||||
|
.B actor \fIname\fR get \fIkey\fR
|
||||||
|
Get an actor-specific configuration value.
|
||||||
|
.TP
|
||||||
|
.B actor \fIname\fR set \fIkey\fR \fIvalue\fR
|
||||||
|
Set an actor-specific configuration value.
|
||||||
|
.TP
|
||||||
|
.B actor \fIname\fR list
|
||||||
|
List all configuration values for a specific actor.
|
||||||
|
.SH SYSTEM KEYS
|
||||||
|
.TP
|
||||||
|
.B system.ar_timer
|
||||||
|
Seconds before idle actor reclamation (default: 60)
|
||||||
|
.TP
|
||||||
|
.B system.actor_memory
|
||||||
|
MB of memory an actor can use; 0 for unbounded (default: 0)
|
||||||
|
.TP
|
||||||
|
.B system.net_service
|
||||||
|
Seconds per network service pull (default: 0.1)
|
||||||
|
.TP
|
||||||
|
.B system.reply_timeout
|
||||||
|
Seconds to hold callback for reply messages; 0 for unbounded (default: 60)
|
||||||
|
.TP
|
||||||
|
.B system.actor_max
|
||||||
|
Maximum number of simultaneous actors (default: 10,000)
|
||||||
|
.TP
|
||||||
|
.B system.stack_max
|
||||||
|
MB of memory each actor's stack can grow to (default: 0)
|
||||||
|
.SH EXAMPLES
|
||||||
|
View a system configuration value:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
cell config get system.ar_timer
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Set a system configuration value:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
cell config set system.net_service 0.2
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Configure an actor-specific setting:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
cell config actor prosperon/_sdl_video set resolution 1920x1080
|
||||||
|
cell config actor extramath/spline set precision high
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
List all configurations:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
cell config list
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
List actor-specific configurations:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
cell config actor prosperon/_sdl_video list
|
||||||
|
.fi
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
.I .cell/cell.toml
|
||||||
|
The main configuration file containing all system and actor settings.
|
||||||
|
.SH NOTES
|
||||||
|
Configuration values are automatically parsed to appropriate types:
|
||||||
|
.IP \(bu 2
|
||||||
|
Boolean: true, false
|
||||||
|
.IP \(bu 2
|
||||||
|
Numbers: integers and floats (underscores allowed for readability)
|
||||||
|
.IP \(bu 2
|
||||||
|
Strings: everything else
|
||||||
|
.PP
|
||||||
|
Actor configurations are loaded automatically when an actor starts,
|
||||||
|
merging the actor-specific settings into the actor's args.
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR cell (1),
|
||||||
|
.BR cell-init (1),
|
||||||
|
.BR cell-get (1)
|
||||||
@@ -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.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.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.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.TAU = Math.PI * 2;
|
||||||
math.deg2rad = function (deg) { return deg * 0.0174533; };
|
math.deg2rad = function (deg) { return deg * 0.0174533; };
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
var miniz = this
|
var miniz = this
|
||||||
|
|
||||||
miniz.read[cell.DOC] = `Create a zip reader from the given ArrayBuffer containing an entire ZIP archive.
|
miniz.read[cell.DOC] = `Create a zip reader from the given ArrayBuffer containing an entire ZIP archive.
|
||||||
Return undefined if the data is invalid.
|
Return null if the data is invalid.
|
||||||
|
|
||||||
:param data: An ArrayBuffer with the entire ZIP file.
|
:param data: An ArrayBuffer with the entire ZIP file.
|
||||||
:return: A 'zip reader' object with methods for reading from the archive (mod, exists, slurp).
|
:return: A 'zip reader' object with methods for reading from the archive (mod, exists, slurp).
|
||||||
`
|
`
|
||||||
|
|
||||||
miniz.write[cell.DOC] = `Create a zip writer that writes to the specified file path. Overwrites the file if
|
miniz.write[cell.DOC] = `Create a zip writer that writes to the specified file path. Overwrites the file if
|
||||||
it already exists. Return undefined on error.
|
it already exists. Return null on error.
|
||||||
|
|
||||||
:param path: The file path where the ZIP archive will be written.
|
:param path: The file path where the ZIP archive will be written.
|
||||||
:return: A 'zip writer' object with methods for adding files to the archive (add_file).
|
:return: A 'zip writer' object with methods for adding files to the archive (add_file).
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ uses.download = function()
|
|||||||
} else if (!io.exists(cache_path)) {
|
} else if (!io.exists(cache_path)) {
|
||||||
log.console(`${mod}: cache missing, will download`)
|
log.console(`${mod}: cache missing, will download`)
|
||||||
need_download = true
|
need_download = true
|
||||||
} else if (remote_commit && (!lock.modules[mod].commit || lock.modules[mod].commit !== remote_commit)) {
|
} else if (remote_commit && (!lock.modules[mod].commit || lock.modules[mod].commit != remote_commit)) {
|
||||||
log.console(`${mod}: remote has new commit`)
|
log.console(`${mod}: remote has new commit`)
|
||||||
log.console(` local: ${lock.modules[mod].commit || 'unknown'}`)
|
log.console(` local: ${lock.modules[mod].commit || 'unknown'}`)
|
||||||
log.console(` remote: ${remote_commit}`)
|
log.console(` remote: ${remote_commit}`)
|
||||||
@@ -74,7 +74,7 @@ uses.download = function()
|
|||||||
var hash = crypto.hash(zip)
|
var hash = crypto.hash(zip)
|
||||||
var hash_b32 = text(hash, "t")
|
var hash_b32 = text(hash, "t")
|
||||||
|
|
||||||
if (hash_b32 !== lock.modules[mod].hash) {
|
if (hash_b32 != lock.modules[mod].hash) {
|
||||||
log.console(`${mod}: hash mismatch, will redownload`)
|
log.console(`${mod}: hash mismatch, will redownload`)
|
||||||
log.console(` expected: ${lock.modules[mod].hash}`)
|
log.console(` expected: ${lock.modules[mod].hash}`)
|
||||||
log.console(` actual: ${hash_b32}`)
|
log.console(` actual: ${hash_b32}`)
|
||||||
@@ -136,7 +136,7 @@ uses.download = function()
|
|||||||
|
|
||||||
// Strip the module name prefix if present
|
// Strip the module name prefix if present
|
||||||
var prefix = mod + '/'
|
var prefix = mod + '/'
|
||||||
if (filename.indexOf(prefix) === 0)
|
if (filename.indexOf(prefix) == 0)
|
||||||
filename = filename.substring(prefix.length)
|
filename = filename.substring(prefix.length)
|
||||||
|
|
||||||
// Skip if filename is empty after stripping
|
// Skip if filename is empty after stripping
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ function resolve_relative(base, relative) {
|
|||||||
|
|
||||||
for (var i = 0; i < rel_parts.length; i++) {
|
for (var i = 0; i < rel_parts.length; i++) {
|
||||||
var part = rel_parts[i]
|
var part = rel_parts[i]
|
||||||
if (part === '.') {
|
if (part == '.') {
|
||||||
continue
|
continue
|
||||||
} else if (part === '..') {
|
} else if (part == '..') {
|
||||||
parts.pop()
|
parts.pop()
|
||||||
} else {
|
} else {
|
||||||
parts.push(part)
|
parts.push(part)
|
||||||
@@ -82,7 +82,7 @@ ModuleResolver.check_alias = function(request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for single-alias fallback
|
// Check for single-alias fallback
|
||||||
if (config.dependencies && Object.keys(config.dependencies).length === 1) {
|
if (config.dependencies && Object.keys(config.dependencies).length == 1) {
|
||||||
// If only one dependency and no local file matches, route there
|
// If only one dependency and no local file matches, route there
|
||||||
var only_dep = Object.keys(config.dependencies)[0]
|
var only_dep = Object.keys(config.dependencies)[0]
|
||||||
return '/' + only_dep + '/' + request
|
return '/' + only_dep + '/' + request
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ This function serializes JavaScript values (such as numbers, strings, booleans,
|
|||||||
|
|
||||||
nota.decode[cell.DOC] = `Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
|
nota.decode[cell.DOC] = `Decode a NOTA-encoded ArrayBuffer into a JavaScript value.
|
||||||
|
|
||||||
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
|
This function deserializes a NOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns null.
|
||||||
|
|
||||||
:param buffer: An ArrayBuffer containing NOTA-encoded data to decode.
|
:param buffer: An ArrayBuffer containing NOTA-encoded data to decode.
|
||||||
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.
|
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or null if no argument is provided.
|
||||||
`
|
`
|
||||||
|
|
||||||
return nota
|
return nota
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ os.freemem[cell.DOC] = "Return the amount of free system RAM in bytes, if known.
|
|||||||
os.hostname[cell.DOC] = "Return the system's hostname, or an empty string if not available."
|
os.hostname[cell.DOC] = "Return the system's hostname, or an empty string if not available."
|
||||||
os.version[cell.DOC] = "Return the OS or kernel version string, if the platform provides it."
|
os.version[cell.DOC] = "Return the OS or kernel version string, if the platform provides it."
|
||||||
|
|
||||||
os.exit[cell.DOC] = "Exit the application with the specified exit code."
|
|
||||||
os.now[cell.DOC] = "Return current time (in seconds as a float) with high resolution."
|
os.now[cell.DOC] = "Return current time (in seconds as a float) with high resolution."
|
||||||
|
|
||||||
os.power_state[cell.DOC] = "Return a string describing power status: 'on battery', 'charging', 'charged', etc."
|
os.power_state[cell.DOC] = "Return a string describing power status: 'on battery', 'charging', 'charged', etc."
|
||||||
|
|
||||||
os.on[cell.DOC] = "Register a global callback for certain engine-wide or system-level events."
|
|
||||||
os.rusage[cell.DOC] = "Return resource usage stats for this process, if the platform supports it."
|
os.rusage[cell.DOC] = "Return resource usage stats for this process, if the platform supports it."
|
||||||
os.mallinfo[cell.DOC] = "Return detailed memory allocation info (arena size, free blocks, etc.) on some platforms."
|
os.mallinfo[cell.DOC] = "Return detailed memory allocation info (arena size, free blocks, etc.) on some platforms."
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,18 @@
|
|||||||
Each factory returns a **requestor** function as described by the spec.
|
Each factory returns a **requestor** function as described by the spec.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const delay = arg[0] // may be undefined
|
def delay = arg[0] // may be null
|
||||||
|
|
||||||
// ———————————————————————————————————————— helpers
|
// ———————————————————————————————————————— helpers
|
||||||
|
|
||||||
function make_reason (factory, excuse, evidence) {
|
function make_reason (factory, excuse, evidence) {
|
||||||
const reason = new Error(`parseq.${factory}${excuse ? ': ' + excuse : ''}`)
|
def reason = new Error(`parseq.${factory}${excuse ? ': ' + excuse : ''}`)
|
||||||
reason.evidence = evidence
|
reason.evidence = evidence
|
||||||
return reason
|
return reason
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_requestor (fn) {
|
function is_requestor (fn) {
|
||||||
return typeof fn === 'function' && (fn.length === 1 || fn.length === 2)
|
return typeof fn == 'function' && (fn.length == 1 || fn.length == 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_requestors (list, factory) {
|
function check_requestors (list, factory) {
|
||||||
@@ -34,13 +34,13 @@ function check_requestors (list, factory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function check_callback (cb, factory) {
|
function check_callback (cb, factory) {
|
||||||
if (typeof cb !== 'function' || cb.length !== 2)
|
if (typeof cb != 'function' || cb.length != 2)
|
||||||
throw make_reason(factory, 'Not a callback.', cb)
|
throw make_reason(factory, 'Not a callback.', cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
function schedule (fn, seconds) {
|
function schedule (fn, seconds) {
|
||||||
if (seconds === undefined || seconds <= 0) return fn()
|
if (seconds == null || seconds <= 0) return fn()
|
||||||
if (typeof delay === 'function') return delay(fn, seconds)
|
if (typeof delay == 'function') return delay(fn, seconds)
|
||||||
throw make_reason('schedule', '@.delay capability required for timeouts.')
|
throw make_reason('schedule', '@.delay capability required for timeouts.')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,41 +52,41 @@ function run (factory, requestors, initial, action, time_limit, throttle = 0) {
|
|||||||
let timer_cancel
|
let timer_cancel
|
||||||
|
|
||||||
function cancel (reason = make_reason(factory, 'Cancel.')) {
|
function cancel (reason = make_reason(factory, 'Cancel.')) {
|
||||||
if (timer_cancel) timer_cancel(), timer_cancel = undefined
|
if (timer_cancel) timer_cancel(), timer_cancel = null
|
||||||
if (!cancel_list) return
|
if (!cancel_list) return
|
||||||
cancel_list.forEach(c => { try { if (typeof c === 'function') c(reason) } catch (_) {} })
|
cancel_list.forEach(c => { try { if (typeof c == 'function') c(reason) } catch (_) {} })
|
||||||
cancel_list = undefined
|
cancel_list = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_requestor (value) {
|
function start_requestor (value) {
|
||||||
if (!cancel_list || next >= requestors.length) return
|
if (!cancel_list || next >= requestors.length) return
|
||||||
let idx = next++
|
let idx = next++
|
||||||
const req = requestors[idx]
|
def req = requestors[idx]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cancel_list[idx] = req(function req_cb (val, reason) {
|
cancel_list[idx] = req(function req_cb (val, reason) {
|
||||||
if (!cancel_list || idx === undefined) return
|
if (!cancel_list || idx == null) return
|
||||||
cancel_list[idx] = undefined
|
cancel_list[idx] = null
|
||||||
action(val, reason, idx)
|
action(val, reason, idx)
|
||||||
idx = undefined
|
idx = null
|
||||||
if (factory === 'sequence') start_requestor(val)
|
if (factory == 'sequence') start_requestor(val)
|
||||||
else if (throttle) start_requestor(initial)
|
else if (throttle) start_requestor(initial)
|
||||||
}, value)
|
}, value)
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
action(undefined, ex, idx)
|
action(null, ex, idx)
|
||||||
idx = undefined
|
idx = null
|
||||||
if (factory === 'sequence') start_requestor(value)
|
if (factory == 'sequence') start_requestor(value)
|
||||||
else if (throttle) start_requestor(initial)
|
else if (throttle) start_requestor(initial)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time_limit !== undefined) {
|
if (time_limit != null) {
|
||||||
if (typeof time_limit !== 'number' || time_limit < 0)
|
if (typeof time_limit != 'number' || time_limit < 0)
|
||||||
throw make_reason(factory, 'Bad time limit.', time_limit)
|
throw make_reason(factory, 'Bad time limit.', time_limit)
|
||||||
if (time_limit > 0) timer_cancel = schedule(() => cancel(make_reason(factory, 'Timeout.', time_limit)), time_limit)
|
if (time_limit > 0) timer_cancel = schedule(() => cancel(make_reason(factory, 'Timeout.', time_limit)), time_limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
const concurrent = throttle ? Math.min(throttle, requestors.length) : requestors.length
|
def concurrent = throttle ? Math.min(throttle, requestors.length) : requestors.length
|
||||||
for (let i = 0; i < concurrent; i++) start_requestor(initial)
|
for (let i = 0; i < concurrent; i++) start_requestor(initial)
|
||||||
|
|
||||||
return cancel
|
return cancel
|
||||||
@@ -96,9 +96,9 @@ function run (factory, requestors, initial, action, time_limit, throttle = 0) {
|
|||||||
|
|
||||||
function _normalize (collection, factory) {
|
function _normalize (collection, factory) {
|
||||||
if (Array.isArray(collection)) return { names: null, list: collection }
|
if (Array.isArray(collection)) return { names: null, list: collection }
|
||||||
if (collection && typeof collection === 'object') {
|
if (collection && typeof collection == 'object') {
|
||||||
const names = Object.keys(collection)
|
def names = Object.keys(collection)
|
||||||
const list = names.map(k => collection[k]).filter(is_requestor)
|
def list = names.map(k => collection[k]).filter(is_requestor)
|
||||||
return { names, list }
|
return { names, list }
|
||||||
}
|
}
|
||||||
throw make_reason(factory, 'Expected array or record.', collection)
|
throw make_reason(factory, 'Expected array or record.', collection)
|
||||||
@@ -106,29 +106,29 @@ function _normalize (collection, factory) {
|
|||||||
|
|
||||||
function _denormalize (names, list) {
|
function _denormalize (names, list) {
|
||||||
if (!names) return list
|
if (!names) return list
|
||||||
const obj = Object.create(null)
|
def obj = Object.create(null)
|
||||||
names.forEach((k, i) => { obj[k] = list[i] })
|
names.forEach((k, i) => { obj[k] = list[i] })
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
function par_all (collection, time_limit, throttle) {
|
function par_all (collection, time_limit, throttle) {
|
||||||
const factory = 'par_all'
|
def factory = 'par_all'
|
||||||
const { names, list } = _normalize(collection, factory)
|
def { names, list } = _normalize(collection, factory)
|
||||||
if (list.length === 0) return (cb, v) => cb(names ? {} : [])
|
if (list.length == 0) return (cb, v) => cb(names ? {} : [])
|
||||||
check_requestors(list, factory)
|
check_requestors(list, factory)
|
||||||
|
|
||||||
return function par_all_req (cb, initial) {
|
return function par_all_req (cb, initial) {
|
||||||
check_callback(cb, factory)
|
check_callback(cb, factory)
|
||||||
let pending = list.length
|
let pending = list.length
|
||||||
const results = new Array(list.length)
|
def results = new Array(list.length)
|
||||||
|
|
||||||
const cancel = run(factory, list, initial, (val, reason, idx) => {
|
def cancel = run(factory, list, initial, (val, reason, idx) => {
|
||||||
if (val === undefined) {
|
if (val == null) {
|
||||||
cancel(reason)
|
cancel(reason)
|
||||||
return cb(undefined, reason)
|
return cb(null, reason)
|
||||||
}
|
}
|
||||||
results[idx] = val
|
results[idx] = val
|
||||||
if (--pending === 0) cb(_denormalize(names, results))
|
if (--pending == 0) cb(_denormalize(names, results))
|
||||||
}, time_limit, throttle)
|
}, time_limit, throttle)
|
||||||
|
|
||||||
return cancel
|
return cancel
|
||||||
@@ -136,24 +136,24 @@ function par_all (collection, time_limit, throttle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function par_any (collection, time_limit, throttle) {
|
function par_any (collection, time_limit, throttle) {
|
||||||
const factory = 'par_any'
|
def factory = 'par_any'
|
||||||
const { names, list } = _normalize(collection, factory)
|
def { names, list } = _normalize(collection, factory)
|
||||||
if (list.length === 0) return (cb, v) => cb(names ? {} : [])
|
if (list.length == 0) return (cb, v) => cb(names ? {} : [])
|
||||||
check_requestors(list, factory)
|
check_requestors(list, factory)
|
||||||
|
|
||||||
return function par_any_req (cb, initial) {
|
return function par_any_req (cb, initial) {
|
||||||
check_callback(cb, factory)
|
check_callback(cb, factory)
|
||||||
let pending = list.length
|
let pending = list.length
|
||||||
const successes = new Array(list.length)
|
def successes = new Array(list.length)
|
||||||
|
|
||||||
const cancel = run(factory, list, initial, (val, reason, idx) => {
|
def cancel = run(factory, list, initial, (val, reason, idx) => {
|
||||||
pending--
|
pending--
|
||||||
if (val !== undefined) successes[idx] = val
|
if (val != null) successes[idx] = val
|
||||||
if (successes.some(v => v !== undefined)) {
|
if (successes.some(v => v != null)) {
|
||||||
if (!pending) cancel(make_reason(factory, 'Finished.'))
|
if (!pending) cancel(make_reason(factory, 'Finished.'))
|
||||||
return cb(_denormalize(names, successes.filter(v => v !== undefined)))
|
return cb(_denormalize(names, successes.filter(v => v != null)))
|
||||||
}
|
}
|
||||||
if (!pending) cb(undefined, make_reason(factory, 'No successes.'))
|
if (!pending) cb(null, make_reason(factory, 'No successes.'))
|
||||||
}, time_limit, throttle)
|
}, time_limit, throttle)
|
||||||
|
|
||||||
return cancel
|
return cancel
|
||||||
@@ -161,24 +161,24 @@ function par_any (collection, time_limit, throttle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function race (list, time_limit, throttle) {
|
function race (list, time_limit, throttle) {
|
||||||
const factory = throttle === 1 ? 'fallback' : 'race'
|
def factory = throttle == 1 ? 'fallback' : 'race'
|
||||||
if (!Array.isArray(list) || list.length === 0)
|
if (!Array.isArray(list) || list.length == 0)
|
||||||
throw make_reason(factory, 'No requestors.')
|
throw make_reason(factory, 'No requestors.')
|
||||||
check_requestors(list, factory)
|
check_requestors(list, factory)
|
||||||
|
|
||||||
return function race_req (cb, initial) {
|
return function race_req (cb, initial) {
|
||||||
check_callback(cb, factory)
|
check_callback(cb, factory)
|
||||||
let done = false
|
let done = false
|
||||||
const cancel = run(factory, list, initial, (val, reason, idx) => {
|
def cancel = run(factory, list, initial, (val, reason, idx) => {
|
||||||
if (done) return
|
if (done) return
|
||||||
if (val !== undefined) {
|
if (val != null) {
|
||||||
done = true
|
done = true
|
||||||
cancel(make_reason(factory, 'Loser.', idx))
|
cancel(make_reason(factory, 'Loser.', idx))
|
||||||
cb(val)
|
cb(val)
|
||||||
} else if (--list.length === 0) {
|
} else if (--list.length == 0) {
|
||||||
done = true
|
done = true
|
||||||
cancel(reason)
|
cancel(reason)
|
||||||
cb(undefined, reason)
|
cb(null, reason)
|
||||||
}
|
}
|
||||||
}, time_limit, throttle)
|
}, time_limit, throttle)
|
||||||
return cancel
|
return cancel
|
||||||
@@ -190,10 +190,10 @@ function fallback (list, time_limit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sequence (list, time_limit) {
|
function sequence (list, time_limit) {
|
||||||
const factory = 'sequence'
|
def factory = 'sequence'
|
||||||
if (!Array.isArray(list)) throw make_reason(factory, 'Not an array.', list)
|
if (!Array.isArray(list)) throw make_reason(factory, 'Not an array.', list)
|
||||||
check_requestors(list, factory)
|
check_requestors(list, factory)
|
||||||
if (list.length === 0) return (cb, v) => cb(v)
|
if (list.length == 0) return (cb, v) => cb(v)
|
||||||
|
|
||||||
return function sequence_req (cb, initial) {
|
return function sequence_req (cb, initial) {
|
||||||
check_callback(cb, factory)
|
check_callback(cb, factory)
|
||||||
@@ -203,11 +203,11 @@ function sequence (list, time_limit) {
|
|||||||
if (idx >= list.length) return cb(value)
|
if (idx >= list.length) return cb(value)
|
||||||
try {
|
try {
|
||||||
list[idx++](function seq_cb (val, reason) {
|
list[idx++](function seq_cb (val, reason) {
|
||||||
if (val === undefined) return cb(undefined, reason)
|
if (val == null) return cb(null, reason)
|
||||||
next(val)
|
next(val)
|
||||||
}, value)
|
}, value)
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
cb(undefined, ex)
|
cb(null, ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Shop.init = function() {
|
|||||||
// Parse module locator (e.g., "git.world/jj/mod@v0.6.3")
|
// Parse module locator (e.g., "git.world/jj/mod@v0.6.3")
|
||||||
Shop.parse_locator = function(locator) {
|
Shop.parse_locator = function(locator) {
|
||||||
var parts = locator.split('@')
|
var parts = locator.split('@')
|
||||||
if (parts.length !== 2) {
|
if (parts.length != 2) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
130
scripts/test.ce
130
scripts/test.ce
@@ -1,114 +1,30 @@
|
|||||||
// Test runner - runs test suites in parallel and reports results
|
// Test runner - runs test suites in parallel and reports results
|
||||||
|
|
||||||
var parseq = use("parseq");
|
var def = arg
|
||||||
var time = use("time");
|
|
||||||
|
|
||||||
// Get test names from command line arguments
|
if (arg.length == 0)
|
||||||
var tests = arg || [];
|
arg = [
|
||||||
|
'send',
|
||||||
|
'stop',
|
||||||
|
'blob',
|
||||||
|
'clock',
|
||||||
|
'couple',
|
||||||
|
'disrupt',
|
||||||
|
'empty', // this one should be an error
|
||||||
|
'text',
|
||||||
|
'http',
|
||||||
|
'use',
|
||||||
|
'parseq',
|
||||||
|
'kill'
|
||||||
|
]
|
||||||
|
|
||||||
// Track overall results
|
for (var test of def)
|
||||||
var totalPassed = 0;
|
$_.start(e => {
|
||||||
var totalFailed = 0;
|
|
||||||
var totalTests = 0;
|
|
||||||
var allFailures = [];
|
|
||||||
var startTime = time.number();
|
|
||||||
|
|
||||||
// Create a requestor for each test
|
|
||||||
function run_test_requestor(testName) {
|
|
||||||
return function (cb, val) {
|
|
||||||
// Start the test actor
|
|
||||||
$_.start(function (greet) {
|
|
||||||
log.console('senging start to ' + json.encode(greet))
|
|
||||||
// Send run_tests message
|
|
||||||
send(greet.actor, {
|
|
||||||
type: 'run_tests',
|
|
||||||
test_name: testName
|
|
||||||
}, function (result) {
|
|
||||||
// Handle test results
|
|
||||||
if (result && result.type === 'test_results') {
|
|
||||||
cb(result);
|
|
||||||
} else {
|
|
||||||
cb(null, "Test " + testName + " did not return valid results");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, "tests/" + testName, $_);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build array of requestors
|
|
||||||
var requestors = tests.map(function (t) {
|
|
||||||
return run_test_requestor(t);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run tests in parallel
|
|
||||||
if (requestors.length === 0) {
|
|
||||||
log.error("No tests specified. Usage: cell test <test1> <test2> ...");
|
|
||||||
quit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var concurrency = 5;
|
|
||||||
var all_tests_job = parseq.par_all(requestors, undefined, concurrency);
|
|
||||||
|
|
||||||
// Handle results
|
|
||||||
all_tests_job(function (results, reason) {
|
|
||||||
if (!results) {
|
|
||||||
log.error("\n❌ Test suite failed:", reason);
|
|
||||||
quit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aggregate results
|
|
||||||
log.console("\n" + "=".repeat(60));
|
|
||||||
log.console("TEST RESULTS");
|
|
||||||
log.console("=".repeat(60));
|
|
||||||
|
|
||||||
for (var i = 0; i < tests.length; i++) {
|
|
||||||
var result = results[i];
|
|
||||||
var testName = tests[i];
|
|
||||||
|
|
||||||
if (result && result.type === 'test_results') {
|
}, 'tests/' + test, $_)
|
||||||
totalPassed += result.passed;
|
|
||||||
totalFailed += result.failed;
|
|
||||||
totalTests += result.total;
|
|
||||||
|
|
||||||
var status = result.failed === 0 ? "✅ PASSED" : "❌ FAILED";
|
|
||||||
log.console("\n" + testName + ": " + status);
|
|
||||||
log.console(" Passed: " + result.passed + "/" + result.total);
|
|
||||||
|
|
||||||
if (result.failures && result.failures.length > 0) {
|
|
||||||
log.console(" Failures:");
|
|
||||||
for (var j = 0; j < result.failures.length; j++) {
|
|
||||||
var failure = result.failures[j];
|
|
||||||
allFailures.push({test: testName, failure: failure});
|
|
||||||
log.console(" - " + failure.name);
|
|
||||||
if (failure.error) {
|
|
||||||
log.console(" " + failure.error.split("\n").join("\n "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.duration) {
|
|
||||||
log.console(" Duration: " + result.duration + "ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary
|
$_.delay($_.stop, 1)
|
||||||
var elapsed = time.now() - startTime;
|
|
||||||
log.console("\n" + "=".repeat(60));
|
|
||||||
log.console("SUMMARY");
|
|
||||||
log.console("=".repeat(60));
|
|
||||||
log.console("Total: " + totalPassed + "/" + totalTests + " tests passed");
|
|
||||||
log.console("Failed: " + totalFailed + " tests");
|
|
||||||
log.console("Time: " + elapsed + "ms");
|
|
||||||
log.console("=".repeat(60) + "\n");
|
|
||||||
|
|
||||||
// Exit with appropriate code
|
|
||||||
quit(totalFailed === 0 ? 0 : 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Timeout protection
|
$_.receiver(e => {
|
||||||
$_.delay(function() {
|
log.console(json.encode(e))
|
||||||
log.error("\n⏰ TEST TIMEOUT: Tests did not complete within 30 seconds");
|
})
|
||||||
quit(1);
|
|
||||||
}, 30);
|
|
||||||
246
scripts/text.cm
246
scripts/text.cm
@@ -3,6 +3,9 @@
|
|||||||
/* -------- helper functions ----------------------------------------- */
|
/* -------- helper functions ----------------------------------------- */
|
||||||
|
|
||||||
var blob = use('blob')
|
var blob = use('blob')
|
||||||
|
var utf8 = use('utf8')
|
||||||
|
|
||||||
|
var that = this
|
||||||
|
|
||||||
// Convert number to string with given radix
|
// Convert number to string with given radix
|
||||||
function to_radix(num, radix) {
|
function to_radix(num, radix) {
|
||||||
@@ -14,7 +17,7 @@ function to_radix(num, radix) {
|
|||||||
var negative = n < 0;
|
var negative = n < 0;
|
||||||
n = Math.abs(n);
|
n = Math.abs(n);
|
||||||
|
|
||||||
if (n === 0) return "0";
|
if (n == 0) return "0";
|
||||||
|
|
||||||
while (n > 0) {
|
while (n > 0) {
|
||||||
result = digits[n % radix] + result;
|
result = digits[n % radix] + result;
|
||||||
@@ -26,9 +29,9 @@ function to_radix(num, radix) {
|
|||||||
|
|
||||||
// Insert separator every n digits from right
|
// Insert separator every n digits from right
|
||||||
function add_separator(str, sep, n) {
|
function add_separator(str, sep, n) {
|
||||||
if (!n || n === 0) return str;
|
if (!n || n == 0) return str;
|
||||||
|
|
||||||
var negative = str[0] === '-';
|
var negative = str[0] == '-';
|
||||||
if (negative) str = str.substring(1);
|
if (negative) str = str.substring(1);
|
||||||
|
|
||||||
var parts = str.split('.');
|
var parts = str.split('.');
|
||||||
@@ -38,7 +41,7 @@ function add_separator(str, sep, n) {
|
|||||||
// Add separators to integer part
|
// Add separators to integer part
|
||||||
var result = "";
|
var result = "";
|
||||||
for (var i = integer.length - 1, count = 0; i >= 0; i--) {
|
for (var i = integer.length - 1, count = 0; i >= 0; i--) {
|
||||||
if (count === n && i !== integer.length - 1) {
|
if (count == n && i != integer.length - 1) {
|
||||||
result = sep + result;
|
result = sep + result;
|
||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
@@ -52,14 +55,14 @@ function add_separator(str, sep, n) {
|
|||||||
|
|
||||||
// Format number with separator from left
|
// Format number with separator from left
|
||||||
function add_separator_left(str, sep, n) {
|
function add_separator_left(str, sep, n) {
|
||||||
if (!n || n === 0) return str;
|
if (!n || n == 0) return str;
|
||||||
|
|
||||||
var negative = str[0] === '-';
|
var negative = str[0] == '-';
|
||||||
if (negative) str = str.substring(1);
|
if (negative) str = str.substring(1);
|
||||||
|
|
||||||
var result = "";
|
var result = "";
|
||||||
for (var i = 0, count = 0; i < str.length; i++) {
|
for (var i = 0, count = 0; i < str.length; i++) {
|
||||||
if (count === n && i !== 0) {
|
if (count == n && i != 0) {
|
||||||
result += sep;
|
result += sep;
|
||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
@@ -72,7 +75,7 @@ function add_separator_left(str, sep, n) {
|
|||||||
|
|
||||||
/* -------- main text function --------------------------------------- */
|
/* -------- main text function --------------------------------------- */
|
||||||
|
|
||||||
function text() {
|
function text(...arguments) {
|
||||||
var arg = arguments[0];
|
var arg = arguments[0];
|
||||||
|
|
||||||
// Handle blob conversion
|
// Handle blob conversion
|
||||||
@@ -84,7 +87,7 @@ function text() {
|
|||||||
var bit_length = arg.length;
|
var bit_length = arg.length;
|
||||||
var result = "";
|
var result = "";
|
||||||
|
|
||||||
if (typeof format === 'string') {
|
if (typeof format == 'string') {
|
||||||
// Extract style from format
|
// Extract style from format
|
||||||
var style = '';
|
var style = '';
|
||||||
for (var i = 0; i < format.length; i++) {
|
for (var i = 0; i < format.length; i++) {
|
||||||
@@ -97,54 +100,10 @@ function text() {
|
|||||||
// Handle blob encoding styles
|
// Handle blob encoding styles
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'h': // hexadecimal
|
case 'h': // hexadecimal
|
||||||
// Read 8 bits at a time for full bytes
|
return that.blob_to_hex(arg);
|
||||||
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;
|
|
||||||
|
|
||||||
case 't': // base32
|
case 't': // base32
|
||||||
var b32_digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
return that.blob_to_base32(arg);
|
||||||
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;
|
|
||||||
|
|
||||||
case 'b': // binary
|
case 'b': // binary
|
||||||
for (var i = 0; i < bit_length; i++) {
|
for (var i = 0; i < bit_length; i++) {
|
||||||
@@ -161,7 +120,7 @@ function text() {
|
|||||||
value = (value << 1) | (bit ? 1 : 0);
|
value = (value << 1) | (bit ? 1 : 0);
|
||||||
bits++;
|
bits++;
|
||||||
|
|
||||||
if (bits === 3) {
|
if (bits == 3) {
|
||||||
result += value.toString();
|
result += value.toString();
|
||||||
bits = 0;
|
bits = 0;
|
||||||
value = 0;
|
value = 0;
|
||||||
@@ -179,126 +138,56 @@ function text() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default: interpret as UTF-8 text
|
// Default: interpret as UTF-8 text
|
||||||
var byte_count = Math.floor(bit_length / 8);
|
// Use the utf8 module to decode the blob
|
||||||
var bytes = [];
|
return utf8.decode(arg);
|
||||||
|
|
||||||
// Read bytes from the blob
|
|
||||||
for (var i = 0; i < byte_count; i++) {
|
|
||||||
var byte_val = 0;
|
|
||||||
for (var j = 0; j < 8; j++) {
|
|
||||||
var bit_pos = i * 8 + j;
|
|
||||||
var bit = arg.read_logical(bit_pos);
|
|
||||||
if (bit) byte_val |= (1 << j);
|
|
||||||
}
|
|
||||||
bytes.push(byte_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert bytes to UTF-8 string
|
|
||||||
var result = "";
|
|
||||||
var i = 0;
|
|
||||||
while (i < bytes.length) {
|
|
||||||
var b1 = bytes[i];
|
|
||||||
var codepoint;
|
|
||||||
var nextI;
|
|
||||||
|
|
||||||
if (b1 < 0x80) {
|
|
||||||
// 1-byte ASCII
|
|
||||||
codepoint = b1;
|
|
||||||
nextI = i + 1;
|
|
||||||
} else if (b1 < 0xC0) {
|
|
||||||
// Invalid start byte, treat as replacement character
|
|
||||||
codepoint = 0xFFFD;
|
|
||||||
nextI = i + 1;
|
|
||||||
} else if (b1 < 0xE0) {
|
|
||||||
// 2-byte sequence
|
|
||||||
if (i + 1 < bytes.length && (bytes[i + 1] & 0xC0) === 0x80) {
|
|
||||||
codepoint = ((b1 & 0x1F) << 6) | (bytes[i + 1] & 0x3F);
|
|
||||||
nextI = i + 2;
|
|
||||||
} else {
|
|
||||||
codepoint = 0xFFFD;
|
|
||||||
nextI = i + 1;
|
|
||||||
}
|
|
||||||
} else if (b1 < 0xF0) {
|
|
||||||
// 3-byte sequence
|
|
||||||
if (i + 2 < bytes.length &&
|
|
||||||
(bytes[i + 1] & 0xC0) === 0x80 &&
|
|
||||||
(bytes[i + 2] & 0xC0) === 0x80) {
|
|
||||||
codepoint = ((b1 & 0x0F) << 12) |
|
|
||||||
((bytes[i + 1] & 0x3F) << 6) |
|
|
||||||
(bytes[i + 2] & 0x3F);
|
|
||||||
nextI = i + 3;
|
|
||||||
} else {
|
|
||||||
codepoint = 0xFFFD;
|
|
||||||
nextI = i + 1;
|
|
||||||
}
|
|
||||||
} else if (b1 < 0xF8) {
|
|
||||||
// 4-byte sequence
|
|
||||||
if (i + 3 < bytes.length &&
|
|
||||||
(bytes[i + 1] & 0xC0) === 0x80 &&
|
|
||||||
(bytes[i + 2] & 0xC0) === 0x80 &&
|
|
||||||
(bytes[i + 3] & 0xC0) === 0x80) {
|
|
||||||
codepoint = ((b1 & 0x07) << 18) |
|
|
||||||
((bytes[i + 1] & 0x3F) << 12) |
|
|
||||||
((bytes[i + 2] & 0x3F) << 6) |
|
|
||||||
(bytes[i + 3] & 0x3F);
|
|
||||||
nextI = i + 4;
|
|
||||||
} else {
|
|
||||||
codepoint = 0xFFFD;
|
|
||||||
nextI = i + 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Invalid start byte
|
|
||||||
codepoint = 0xFFFD;
|
|
||||||
nextI = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert codepoint to string
|
|
||||||
if (codepoint <= 0xFFFF) {
|
|
||||||
result += String.fromCharCode(codepoint);
|
|
||||||
} else if (codepoint <= 0x10FFFF) {
|
|
||||||
// Convert to surrogate pair for JavaScript
|
|
||||||
codepoint -= 0x10000;
|
|
||||||
result += String.fromCharCode(0xD800 + (codepoint >> 10));
|
|
||||||
result += String.fromCharCode(0xDC00 + (codepoint & 0x3FF));
|
|
||||||
} else {
|
|
||||||
result += String.fromCharCode(0xFFFD); // Replacement character
|
|
||||||
}
|
|
||||||
|
|
||||||
i = nextI;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle array conversion
|
// Handle array conversion
|
||||||
if (Array.isArray(arg)) {
|
if (Array.isArray(arg)) {
|
||||||
var separator = arguments[1] || "";
|
var separator = arguments[1] || "";
|
||||||
var result = "";
|
|
||||||
|
// Check if all items are valid codepoints
|
||||||
|
var all_codepoints = true;
|
||||||
for (var i = 0; i < arg.length; i++) {
|
for (var i = 0; i < arg.length; i++) {
|
||||||
if (i > 0) result += separator;
|
|
||||||
|
|
||||||
var item = arg[i];
|
var item = arg[i];
|
||||||
if (typeof item === 'number' && item >= 0 && item <= 0x10FFFF && item === Math.floor(item)) {
|
if (!(typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == Math.floor(item))) {
|
||||||
// Unicode codepoint
|
all_codepoints = false;
|
||||||
result += String.fromCharCode(item);
|
break;
|
||||||
} else {
|
|
||||||
result += String(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
if (all_codepoints && separator == "") {
|
||||||
|
// Use utf8 module to convert codepoints to string
|
||||||
|
return utf8.from_codepoints(arg);
|
||||||
|
} else {
|
||||||
|
// General array to string conversion
|
||||||
|
var result = "";
|
||||||
|
for (var i = 0; i < arg.length; i++) {
|
||||||
|
if (i > 0) result += separator;
|
||||||
|
|
||||||
|
var item = arg[i];
|
||||||
|
if (typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == Math.floor(item)) {
|
||||||
|
// Single codepoint - use utf8 module
|
||||||
|
result += utf8.from_codepoints([item]);
|
||||||
|
} else {
|
||||||
|
result += String(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle number conversion
|
// Handle number conversion
|
||||||
if (typeof arg === 'number') {
|
if (typeof arg == 'number') {
|
||||||
var format = arguments[1];
|
var format = arguments[1];
|
||||||
|
|
||||||
// Simple radix conversion
|
// Simple radix conversion
|
||||||
if (typeof format === 'number') {
|
if (typeof format == 'number') {
|
||||||
return to_radix(arg, format);
|
return to_radix(arg, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format string conversion
|
// Format string conversion
|
||||||
if (typeof format === 'string') {
|
if (typeof format == 'string') {
|
||||||
return format_number(arg, format);
|
return format_number(arg, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,13 +196,13 @@ function text() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle text operations
|
// Handle text operations
|
||||||
if (typeof arg === 'string') {
|
if (typeof arg == 'string') {
|
||||||
if (arguments.length === 1) return arg;
|
if (arguments.length == 1) return arg;
|
||||||
|
|
||||||
var from = arguments[1];
|
var from = arguments[1];
|
||||||
var to = arguments[2];
|
var to = arguments[2];
|
||||||
|
|
||||||
if (typeof from !== 'number' || typeof to !== 'number') return arg;
|
if (typeof from != 'number' || typeof to != 'number') return arg;
|
||||||
|
|
||||||
var len = arg.length;
|
var len = arg.length;
|
||||||
|
|
||||||
@@ -322,8 +211,8 @@ function text() {
|
|||||||
if (to < 0) to += len;
|
if (to < 0) to += len;
|
||||||
|
|
||||||
// Default values
|
// Default values
|
||||||
if (from === undefined) from = 0;
|
if (from == null) from = 0;
|
||||||
if (to === undefined) to = len;
|
if (to == null) to = len;
|
||||||
|
|
||||||
// Validate range
|
// Validate range
|
||||||
if (from < 0 || from > to || to > len) return null;
|
if (from < 0 || from > to || to > len) return null;
|
||||||
@@ -372,8 +261,8 @@ function format_number(num, format) {
|
|||||||
if (i < format.length) return null;
|
if (i < format.length) return null;
|
||||||
|
|
||||||
// Real number styles
|
// Real number styles
|
||||||
if (style === 'e' || style === 'n' || style === 's' ||
|
if (style == 'e' || style == 'n' || style == 's' ||
|
||||||
style === 'u' || style === 'd' || style === 'v' || style === 'l') {
|
style == 'u' || style == 'd' || style == 'v' || style == 'l') {
|
||||||
|
|
||||||
var decimal_point = '.';
|
var decimal_point = '.';
|
||||||
var separator = '';
|
var separator = '';
|
||||||
@@ -425,15 +314,15 @@ function format_number(num, format) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (separation === 0) separation = default_separation;
|
if (separation == 0) separation = default_separation;
|
||||||
if (places === 0 && style !== 'e' && style !== 'n') places = default_places;
|
if (places == 0 && style != 'e' && style != 'n') places = default_places;
|
||||||
|
|
||||||
// Format the number
|
// Format the number
|
||||||
if (style === 'e') {
|
if (style == 'e') {
|
||||||
// Scientific notation
|
// Scientific notation
|
||||||
var str = places > 0 ? num.toExponential(places) : num.toExponential();
|
var str = places > 0 ? num.toExponential(places) : num.toExponential();
|
||||||
return str;
|
return str;
|
||||||
} else if (style === 'n' && (Math.abs(num) >= 1e21 || (Math.abs(num) < 1e-6 && num !== 0))) {
|
} else if (style == 'n' && (Math.abs(num) >= 1e21 || (Math.abs(num) < 1e-6 && num != 0))) {
|
||||||
// Use scientific notation for extreme values
|
// Use scientific notation for extreme values
|
||||||
return num.toExponential();
|
return num.toExponential();
|
||||||
} else {
|
} else {
|
||||||
@@ -446,7 +335,7 @@ function format_number(num, format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace decimal point if needed
|
// Replace decimal point if needed
|
||||||
if (decimal_point !== '.') {
|
if (decimal_point != '.') {
|
||||||
str = str.replace('.', decimal_point);
|
str = str.replace('.', decimal_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,8 +349,8 @@ function format_number(num, format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Integer styles
|
// Integer styles
|
||||||
if (style === 'i' || style === 'b' || style === 'o' ||
|
if (style == 'i' || style == 'b' || style == 'o' ||
|
||||||
style === 'h' || style === 't') {
|
style == 'h' || style == 't') {
|
||||||
|
|
||||||
var radix = 10;
|
var radix = 10;
|
||||||
var default_separation = 0;
|
var default_separation = 0;
|
||||||
@@ -495,15 +384,15 @@ function format_number(num, format) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (separation === 0) separation = default_separation;
|
if (separation == 0) separation = default_separation;
|
||||||
if (places === 0) places = default_places;
|
if (places == 0) places = default_places;
|
||||||
|
|
||||||
// Convert to integer
|
// Convert to integer
|
||||||
var n = Math.trunc(num);
|
var n = Math.trunc(num);
|
||||||
var str = to_radix(n, radix).toUpperCase();
|
var str = to_radix(n, radix).toUpperCase();
|
||||||
|
|
||||||
// Pad with zeros if needed
|
// Pad with zeros if needed
|
||||||
var negative = str[0] === '-';
|
var negative = str[0] == '-';
|
||||||
if (negative) str = str.substring(1);
|
if (negative) str = str.substring(1);
|
||||||
|
|
||||||
while (str.length < places) {
|
while (str.length < places) {
|
||||||
@@ -521,13 +410,4 @@ function format_number(num, format) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------- documentation -------------------------------------------- */
|
|
||||||
|
|
||||||
text[cell.DOC] = {
|
|
||||||
doc: "Text conversion and formatting utilities",
|
|
||||||
text: "text(value, ...) → formatted text string"
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -------- exports -------------------------------------------------- */
|
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
@@ -41,10 +41,10 @@ time.week2day = function() { return time.week / time.day; };
|
|||||||
/* leap-year helpers */
|
/* leap-year helpers */
|
||||||
time.yearsize = function yearsize(y)
|
time.yearsize = function yearsize(y)
|
||||||
{
|
{
|
||||||
if (y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0)) return 366;
|
if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) return 366;
|
||||||
return 365;
|
return 365;
|
||||||
};
|
};
|
||||||
time.isleap = function(y) { return time.yearsize(y) === 366; };
|
time.isleap = function(y) { return time.yearsize(y) == 366; };
|
||||||
|
|
||||||
/* timecode utility */
|
/* timecode utility */
|
||||||
time.timecode = function(t, fps = 24)
|
time.timecode = function(t, fps = 24)
|
||||||
@@ -64,7 +64,7 @@ function record(num = now(),
|
|||||||
dst = computer_dst())
|
dst = computer_dst())
|
||||||
{
|
{
|
||||||
/* caller passed an existing record → return it verbatim */
|
/* caller passed an existing record → return it verbatim */
|
||||||
if (typeof num === "object") return num;
|
if (typeof num == "object") return num;
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* convert seconds-since-epoch → broken-down record */
|
/* convert seconds-since-epoch → broken-down record */
|
||||||
@@ -107,7 +107,7 @@ function record(num = now(),
|
|||||||
rec.yday = day;
|
rec.yday = day;
|
||||||
|
|
||||||
/* month & month-day */
|
/* month & month-day */
|
||||||
if (time.yearsize(y) === 366) monthdays[1] = 29;
|
if (time.yearsize(y) == 366) monthdays[1] = 29;
|
||||||
var m = 0;
|
var m = 0;
|
||||||
for (; day >= monthdays[m]; m++) day -= monthdays[m];
|
for (; day >= monthdays[m]; m++) day -= monthdays[m];
|
||||||
rec.month = m;
|
rec.month = m;
|
||||||
@@ -119,11 +119,15 @@ function record(num = now(),
|
|||||||
function number(rec = now())
|
function number(rec = now())
|
||||||
{
|
{
|
||||||
/* fall through for numeric input or implicit “now” */
|
/* fall through for numeric input or implicit “now” */
|
||||||
if (typeof rec === "number") return rec;
|
if (typeof rec == "number") return rec;
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* record → seconds-since-epoch */
|
/* record → seconds-since-epoch */
|
||||||
|
|
||||||
|
log.console(typeof rec)
|
||||||
|
log.console(rec)
|
||||||
|
log.console(rec.minute)
|
||||||
|
|
||||||
var c = 0;
|
var c = 0;
|
||||||
var year = rec.year || 0;
|
var year = rec.year || 0;
|
||||||
var hour = rec.hour || 0;
|
var hour = rec.hour || 0;
|
||||||
@@ -165,15 +169,15 @@ function text(num = now(),
|
|||||||
zone = computer_zone(),
|
zone = computer_zone(),
|
||||||
dst = computer_dst())
|
dst = computer_dst())
|
||||||
{
|
{
|
||||||
var rec = (typeof num === "number") ? record(num, zone, dst) : num;
|
var rec = (typeof num == "number") ? record(num, zone, dst) : num;
|
||||||
zone = rec.zone;
|
zone = rec.zone;
|
||||||
dst = rec.dst;
|
dst = rec.dst;
|
||||||
|
|
||||||
/* am/pm */
|
/* am/pm */
|
||||||
if (fmt.includes("a")) {
|
if (fmt.includes("a")) {
|
||||||
if (rec.hour >= 13) { rec.hour -= 12; fmt = fmt.replaceAll("a", "PM"); }
|
if (rec.hour >= 13) { rec.hour -= 12; fmt = fmt.replaceAll("a", "PM"); }
|
||||||
else if (rec.hour === 12) { fmt = fmt.replaceAll("a", "PM"); }
|
else if (rec.hour == 12) { fmt = fmt.replaceAll("a", "PM"); }
|
||||||
else if (rec.hour === 0) { rec.hour = 12; fmt = fmt.replaceAll("a", "AM"); }
|
else if (rec.hour == 0) { rec.hour = 12; fmt = fmt.replaceAll("a", "AM"); }
|
||||||
else fmt = fmt.replaceAll("a", "AM");
|
else fmt = fmt.replaceAll("a", "AM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ function parse_toml(text) {
|
|||||||
} else if (value.startsWith('[') && value.endsWith(']')) {
|
} else if (value.startsWith('[') && value.endsWith(']')) {
|
||||||
// Array
|
// Array
|
||||||
current_section[key] = parse_array(value)
|
current_section[key] = parse_array(value)
|
||||||
} else if (value === 'true' || value === 'false') {
|
} else if (value == 'true' || value == 'false') {
|
||||||
// Boolean
|
// Boolean
|
||||||
current_section[key] = value === 'true'
|
current_section[key] = value == 'true'
|
||||||
} else if (!isNaN(Number(value))) {
|
} else if (!isNaN(Number(value))) {
|
||||||
// Number
|
// Number
|
||||||
current_section[key] = Number(value)
|
current_section[key] = Number(value)
|
||||||
@@ -70,10 +70,10 @@ function parse_array(str) {
|
|||||||
for (var i = 0; i < str.length; i++) {
|
for (var i = 0; i < str.length; i++) {
|
||||||
var char = str[i]
|
var char = str[i]
|
||||||
|
|
||||||
if (char === '"' && (i === 0 || str[i-1] !== '\\')) {
|
if (char == '"' && (i == 0 || str[i-1] != '\\')) {
|
||||||
in_quotes = !in_quotes
|
in_quotes = !in_quotes
|
||||||
current += char
|
current += char
|
||||||
} else if (char === ',' && !in_quotes) {
|
} else if (char == ',' && !in_quotes) {
|
||||||
items.push(parse_value(current.trim()))
|
items.push(parse_value(current.trim()))
|
||||||
current = ''
|
current = ''
|
||||||
} else {
|
} else {
|
||||||
@@ -91,8 +91,8 @@ function parse_array(str) {
|
|||||||
function parse_value(str) {
|
function parse_value(str) {
|
||||||
if (str.startsWith('"') && str.endsWith('"')) {
|
if (str.startsWith('"') && str.endsWith('"')) {
|
||||||
return str.slice(1, -1).replace(/\\"/g, '"')
|
return str.slice(1, -1).replace(/\\"/g, '"')
|
||||||
} else if (str === 'true' || str === 'false') {
|
} else if (str == 'true' || str == 'false') {
|
||||||
return str === 'true'
|
return str == 'true'
|
||||||
} else if (!isNaN(Number(str))) {
|
} else if (!isNaN(Number(str))) {
|
||||||
return Number(str)
|
return Number(str)
|
||||||
} else {
|
} else {
|
||||||
@@ -104,11 +104,11 @@ function encode_toml(obj) {
|
|||||||
var result = []
|
var result = []
|
||||||
|
|
||||||
function encode_value(value) {
|
function encode_value(value) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value == 'string') {
|
||||||
return '"' + value.replace(/"/g, '\\"') + '"'
|
return '"' + value.replace(/"/g, '\\"') + '"'
|
||||||
} else if (typeof value === 'boolean') {
|
} else if (typeof value == 'boolean') {
|
||||||
return value ? 'true' : 'false'
|
return value ? 'true' : 'false'
|
||||||
} else if (typeof value === 'number') {
|
} else if (typeof value == 'number') {
|
||||||
return String(value)
|
return String(value)
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
var items = []
|
var items = []
|
||||||
@@ -125,7 +125,7 @@ function encode_toml(obj) {
|
|||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
var key = keys[i]
|
var key = keys[i]
|
||||||
var value = obj[key]
|
var value = obj[key]
|
||||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
if (value == null || typeof value != 'object' || Array.isArray(value)) {
|
||||||
result.push(key + ' = ' + encode_value(value))
|
result.push(key + ' = ' + encode_value(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ function encode_toml(obj) {
|
|||||||
var key = keys[i]
|
var key = keys[i]
|
||||||
var value = obj[key]
|
var value = obj[key]
|
||||||
|
|
||||||
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
if (value != null && typeof value == 'object' && !Array.isArray(value)) {
|
||||||
// Nested object - create section
|
// Nested object - create section
|
||||||
var section_path = path ? path + '.' + key : key
|
var section_path = path ? path + '.' + key : key
|
||||||
result.push('[' + section_path + ']')
|
result.push('[' + section_path + ']')
|
||||||
@@ -148,7 +148,7 @@ function encode_toml(obj) {
|
|||||||
for (var j = 0; j < section_keys.length; j++) {
|
for (var j = 0; j < section_keys.length; j++) {
|
||||||
var sk = section_keys[j]
|
var sk = section_keys[j]
|
||||||
var sv = value[sk]
|
var sv = value[sk]
|
||||||
if (sv === null || typeof sv !== 'object' || Array.isArray(sv)) {
|
if (sv == null || typeof sv != 'object' || Array.isArray(sv)) {
|
||||||
result.push(sk + ' = ' + encode_value(sv))
|
result.push(sk + ' = ' + encode_value(sv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ for (var alias in deps_to_check) {
|
|||||||
local_commit: null,
|
local_commit: null,
|
||||||
remote_commit: remote_commit
|
remote_commit: remote_commit
|
||||||
})
|
})
|
||||||
} else if (local_commit !== remote_commit) {
|
} else if (local_commit != remote_commit) {
|
||||||
log.console(" Update available!")
|
log.console(" Update available!")
|
||||||
log.console(" Local: " + local_commit.substring(0, 8))
|
log.console(" Local: " + local_commit.substring(0, 8))
|
||||||
log.console(" Remote: " + remote_commit.substring(0, 8))
|
log.console(" Remote: " + remote_commit.substring(0, 8))
|
||||||
@@ -95,7 +95,7 @@ for (var alias in deps_to_check) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updates_available.length === 0) {
|
if (updates_available.length == 0) {
|
||||||
log.console("\nAll dependencies are up to date!")
|
log.console("\nAll dependencies are up to date!")
|
||||||
$_.stop()
|
$_.stop()
|
||||||
return
|
return
|
||||||
|
|||||||
125
scripts/util.cm
125
scripts/util.cm
@@ -1,138 +1,68 @@
|
|||||||
var util = this
|
var util = this
|
||||||
util[cell.DOC] = `
|
|
||||||
A collection of general-purpose utility functions for object manipulation, merging,
|
|
||||||
deep copying, safe property access, etc.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.deepfreeze = function (obj) {
|
return util
|
||||||
for (var key in obj) {
|
|
||||||
if (typeof obj[key] === "object") Object.deepfreeze(obj[key])
|
|
||||||
}
|
|
||||||
Object.freeze(obj)
|
|
||||||
}
|
|
||||||
util.deepfreeze[cell.DOC] = `
|
|
||||||
:param obj: The object to recursively freeze.
|
|
||||||
:return: None
|
|
||||||
Recursively freeze an object and all of its nested objects so they cannot be modified.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.dainty_assign = function (target, source) {
|
util.dainty_assign = function (target, source) {
|
||||||
Object.keys(source).forEach(function (k) {
|
Object.keys(source).forEach(function (k) {
|
||||||
if (typeof source[k] === "function") return
|
if (typeof source[k] == "function") return
|
||||||
if (!(k in target)) return
|
if (!(k in target)) return
|
||||||
if (Array.isArray(source[k])) target[k] = deep_copy(source[k])
|
if (Array.isArray(source[k])) target[k] = deep_copy(source[k])
|
||||||
else if (Object.isObject(source[k])) Object.dainty_assign(target[k], source[k])
|
else if (Object.isObject(source[k])) Object.dainty_assign(target[k], source[k])
|
||||||
else target[k] = source[k]
|
else target[k] = source[k]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
util.dainty_assign[cell.DOC] = `
|
|
||||||
:param target: The target object whose keys may be updated.
|
|
||||||
:param source: The source object containing new values.
|
|
||||||
:return: None
|
|
||||||
Copy non-function properties from source into matching keys of target without overwriting
|
|
||||||
keys that don't exist in target. Arrays are deep-copied, and objects are recursively assigned.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.get = function (obj, path, defValue) {
|
util.get = function (obj, path, defValue) {
|
||||||
if (!path) return undefined
|
if (!path) return null
|
||||||
var pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)
|
var pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g)
|
||||||
var result = pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj)
|
var result = pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj)
|
||||||
return result === undefined ? defValue : result
|
return result == null ? defValue : result
|
||||||
}
|
}
|
||||||
util.get[cell.DOC] = `
|
|
||||||
:param obj: The object to traverse.
|
|
||||||
:param path: A string like "a.b.c" or an array of path segments.
|
|
||||||
:param defValue: The default value if the property is undefined.
|
|
||||||
:return: The nested property or defValue.
|
|
||||||
Safely retrieve a nested property from obj at path (array or dot-string).
|
|
||||||
Returns defValue if the property is undefined.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.isEmpty = function(o) {
|
util.isEmpty = function(o) {
|
||||||
return Object.keys(o).length === 0
|
return Object.keys(o).length == 0
|
||||||
}
|
}
|
||||||
util.isEmpty[cell.DOC] = `
|
|
||||||
:param o: The object to check.
|
|
||||||
:return: Boolean indicating if the object is empty.
|
|
||||||
Return true if the object has no own properties, otherwise false.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.dig = function (obj, path, def = {}) {
|
util.dig = function (obj, path, deflt = {}) {
|
||||||
var pp = path.split(".")
|
var pp = path.split(".")
|
||||||
for (var i = 0; i < pp.length - 1; i++) {
|
for (var i = 0; i < pp.length - 1; i++) {
|
||||||
obj = obj[pp[i]] = obj[pp[i]] || {}
|
obj = obj[pp[i]] = obj[pp[i]] || {}
|
||||||
}
|
}
|
||||||
obj[pp[pp.length - 1]] = def
|
obj[pp[pp.length - 1]] = deflt
|
||||||
return def
|
return deflt
|
||||||
}
|
}
|
||||||
util.dig[cell.DOC] = `
|
|
||||||
:param obj: The root object to modify.
|
|
||||||
:param path: A dot-string specifying nested objects to create.
|
|
||||||
:param def: The value to store in the final path component, default {}.
|
|
||||||
:return: The assigned final value.
|
|
||||||
Ensure a nested path of objects exists inside obj; create objects if missing, and set
|
|
||||||
the final path component to def.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.access = function (obj, name) {
|
util.access = function (obj, name) {
|
||||||
var dig = name.split(".")
|
var dig = name.split(".")
|
||||||
for (var i of dig) {
|
for (var i of dig) {
|
||||||
obj = obj[i]
|
obj = obj[i]
|
||||||
if (!obj) return undefined
|
if (!obj) return null
|
||||||
}
|
}
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
util.access[cell.DOC] = `
|
|
||||||
:param obj: The object to traverse.
|
|
||||||
:param name: A dot-string path (e.g. "foo.bar.baz").
|
|
||||||
:return: The value at that path, or undefined if missing.
|
|
||||||
Traverse obj by dot-separated path name, returning the final value or undefined
|
|
||||||
if any step is missing.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.mergekey = function (o1, o2, k) {
|
util.mergekey = function (o1, o2, k) {
|
||||||
if (!o2) return
|
if (!o2) return
|
||||||
if (typeof o2[k] === "object") {
|
if (typeof o2[k] == "object") {
|
||||||
if (Array.isArray(o2[k])) o1[k] = deep_copy(o2[k])
|
if (Array.isArray(o2[k])) o1[k] = deep_copy(o2[k])
|
||||||
else {
|
else {
|
||||||
if (!o1[k]) o1[k] = {}
|
if (!o1[k]) o1[k] = {}
|
||||||
if (typeof o1[k] === "object") util.merge(o1[k], o2[k])
|
if (typeof o1[k] == "object") util.merge(o1[k], o2[k])
|
||||||
else o1[k] = o2[k]
|
else o1[k] = o2[k]
|
||||||
}
|
}
|
||||||
} else o1[k] = o2[k]
|
} else o1[k] = o2[k]
|
||||||
}
|
}
|
||||||
util.mergekey[cell.DOC] = `
|
|
||||||
:param o1: The target object.
|
|
||||||
:param o2: The source object.
|
|
||||||
:param k: The key to merge.
|
|
||||||
:return: None
|
|
||||||
Helper for merge, updating key k from o2 into o1. Arrays are deep-copied and objects are
|
|
||||||
recursively merged.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.merge = function (target, ...objs) {
|
util.merge = function (target, ...objs) {
|
||||||
for (var obj of objs) for (var key of Object.keys(obj)) util.mergekey(target, obj, key)
|
for (var obj of objs) for (var key of Object.keys(obj)) util.mergekey(target, obj, key)
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
util.merge[cell.DOC] = `
|
|
||||||
:param target: The target object.
|
|
||||||
:param objs: One or more objects to merge into target.
|
|
||||||
:return: The updated target object.
|
|
||||||
Merge all passed objects into target, copying or merging each key as needed.
|
|
||||||
Arrays are deep-copied, objects are recursively merged, etc.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.copy = function (proto, ...objs) {
|
util.copy = function (proto, ...objs) {
|
||||||
var c = Object.create(proto)
|
var c = Object.create(proto)
|
||||||
for (var obj of objs) Object.mixin(c, obj)
|
for (var obj of objs) Object.mixin(c, obj)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
util.copy[cell.DOC] = `
|
|
||||||
:param proto: The prototype object for the new object.
|
|
||||||
:param objs: One or more objects whose properties will be mixed in.
|
|
||||||
:return: The newly created object.
|
|
||||||
Create a new object with proto as its prototype, then mix in additional objects’ properties.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.obj_lerp = function(a,b,t) {
|
util.obj_lerp = function(a,b,t) {
|
||||||
if (a.lerp) return a.lerp(b, t)
|
if (a.lerp) return a.lerp(b, t)
|
||||||
@@ -142,47 +72,22 @@ util.obj_lerp = function(a,b,t) {
|
|||||||
})
|
})
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
util.obj_lerp[cell.DOC] = `
|
|
||||||
:param a: The start object (its properties must have .lerp()).
|
|
||||||
:param b: The end object (matching properties).
|
|
||||||
:param t: Interpolation factor (0..1).
|
|
||||||
:return: A new object with interpolated properties.
|
|
||||||
Linearly interpolate between two objects a and b by factor t, assuming each property
|
|
||||||
supports .lerp().
|
|
||||||
`
|
|
||||||
|
|
||||||
util.normalizeSpacing = function normalizeSpacing(spacing) {
|
util.normalizeSpacing = function normalizeSpacing(spacing) {
|
||||||
if (typeof spacing === 'number') {
|
if (typeof spacing == 'number') {
|
||||||
return {l: spacing, r: spacing, t: spacing, b: spacing}
|
return {l: spacing, r: spacing, t: spacing, b: spacing}
|
||||||
} else if (Array.isArray(spacing)) {
|
} else if (Array.isArray(spacing)) {
|
||||||
if (spacing.length === 2) {
|
if (spacing.length == 2) {
|
||||||
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
|
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
|
||||||
} else if (spacing.length === 4) {
|
} else if (spacing.length == 4) {
|
||||||
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
|
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
|
||||||
}
|
}
|
||||||
} else if (typeof spacing === 'object') {
|
} else if (typeof spacing == 'object') {
|
||||||
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
|
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
|
||||||
} else {
|
} else {
|
||||||
return {l:0, r:0, t:0, b:0}
|
return {l:0, r:0, t:0, b:0}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
util.normalizeSpacing[cell.DOC] = `
|
|
||||||
:param spacing: A number, an array of length 2 or 4, or an object with l/r/t/b.
|
|
||||||
:return: An object {l, r, t, b}.
|
|
||||||
Normalize any spacing input into a {l, r, t, b} object.
|
|
||||||
`
|
|
||||||
|
|
||||||
util.guid[cell.DOC] = `
|
|
||||||
:return: A random 32-character string (hex).
|
|
||||||
Return a random 32-character hexadecimal UUID-like string (not guaranteed RFC4122-compliant).
|
|
||||||
`
|
|
||||||
|
|
||||||
util.insertion_sort[cell.DOC] = `
|
|
||||||
:param arr: The array to be sorted in-place.
|
|
||||||
:param cmp: Comparison function cmp(a,b)->Number.
|
|
||||||
:return: The same array, sorted in-place.
|
|
||||||
In-place insertion sort of an array using cmp(a,b)->Number for ordering.
|
|
||||||
`
|
|
||||||
|
|
||||||
function deep_copy(from) {
|
function deep_copy(from) {
|
||||||
return json.decode(json.encode(from))
|
return json.decode(json.encode(from))
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ This function serializes JavaScript values (such as numbers, strings, booleans,
|
|||||||
|
|
||||||
wota.decode[cell.DOC] = `Decode a WOTA-encoded ArrayBuffer into a JavaScript value.
|
wota.decode[cell.DOC] = `Decode a WOTA-encoded ArrayBuffer into a JavaScript value.
|
||||||
|
|
||||||
This function deserializes a WOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns undefined.
|
This function deserializes a WOTA-formatted ArrayBuffer into its corresponding JavaScript representation, such as a number, string, boolean, array, object, or ArrayBuffer. If the input is invalid or empty, it returns null.
|
||||||
|
|
||||||
:param buffer: An ArrayBuffer containing WOTA-encoded data to decode.
|
:param buffer: An ArrayBuffer containing WOTA-encoded data to decode.
|
||||||
:param reviver: An optional function that transforms the decoded values.
|
:param reviver: An optional function that transforms the decoded values.
|
||||||
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or undefined if no argument is provided.
|
:return: The decoded JavaScript value (e.g., number, string, boolean, array, object, or ArrayBuffer), or null if no argument is provided.
|
||||||
`
|
`
|
||||||
|
|
||||||
return wota
|
return wota
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ int blob_write_bit(blob *b, int bit_val);
|
|||||||
int blob_write_blob(blob *b, const blob *src);
|
int blob_write_blob(blob *b, const blob *src);
|
||||||
int blob_write_dec64(blob *b, double d);
|
int blob_write_dec64(blob *b, double d);
|
||||||
int blob_write_int64(blob *b, int64_t i);
|
int blob_write_int64(blob *b, int64_t i);
|
||||||
|
int blob_write_fit(blob *b, int64_t value, int len);
|
||||||
int blob_write_pad(blob *b, int block_size);
|
int blob_write_pad(blob *b, int block_size);
|
||||||
int blob_write_text(blob *b, const char *text);
|
int blob_write_text(blob *b, const char *text);
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ int blob_read_bit(const blob *b, size_t pos, int *out_bit);
|
|||||||
blob *blob_read_blob(const blob *b, size_t from, size_t to);
|
blob *blob_read_blob(const blob *b, size_t from, size_t to);
|
||||||
int blob_read_dec64(const blob *b, size_t from, double *out_value);
|
int blob_read_dec64(const blob *b, size_t from, double *out_value);
|
||||||
int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value);
|
int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value);
|
||||||
|
int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value);
|
||||||
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read);
|
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read);
|
||||||
int blob_pad_check(const blob *b, size_t from, int block_size);
|
int blob_pad_check(const blob *b, size_t from, int block_size);
|
||||||
|
|
||||||
@@ -315,6 +317,23 @@ int blob_write_int64(blob *b, int64_t i) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int blob_write_fit(blob *b, int64_t value, int len) {
|
||||||
|
if (!b || b->is_stone) return -1;
|
||||||
|
if (len < 1 || len > 64) return -1;
|
||||||
|
|
||||||
|
// Check if value fits in len bits with sign
|
||||||
|
if (len < 64) {
|
||||||
|
int64_t max = (1LL << (len - 1)) - 1;
|
||||||
|
int64_t min = -(1LL << (len - 1));
|
||||||
|
if (value < min || value > max) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blob_ensure_capacity(b, len) < 0) return -1;
|
||||||
|
copy_bits_fast(&value, b->data, 0, len - 1, (int)b->length);
|
||||||
|
b->length += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int blob_write_pad(blob *b, int block_size) {
|
int blob_write_pad(blob *b, int block_size) {
|
||||||
if (!b || b->is_stone) return -1;
|
if (!b || b->is_stone) return -1;
|
||||||
if (block_size <= 0) return -1;
|
if (block_size <= 0) return -1;
|
||||||
@@ -380,6 +399,24 @@ int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value) {
|
||||||
|
if (!b || !b->is_stone || !out_value) return -1;
|
||||||
|
if (len < 1 || len > 64) return -1;
|
||||||
|
if (from + (size_t)len > b->length) return -1;
|
||||||
|
|
||||||
|
*out_value = 0;
|
||||||
|
copy_bits_fast(b->data, out_value, (int)from, (int)(from + len - 1), 0);
|
||||||
|
|
||||||
|
// Sign extend if necessary (if the high bit is set and len < 64)
|
||||||
|
if (len < 64 && (*out_value & (1LL << (len - 1)))) {
|
||||||
|
// Set all bits above len to 1 for negative numbers
|
||||||
|
int64_t mask = ~((1LL << len) - 1);
|
||||||
|
*out_value |= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) {
|
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) {
|
||||||
if (!b || !b->is_stone || !out_text || !bits_read) return -1;
|
if (!b || !b->is_stone || !out_text || !bits_read) return -1;
|
||||||
// Need at least 64 bits for length prefix
|
// Need at least 64 bits for length prefix
|
||||||
|
|||||||
1510
source/cell.c
1510
source/cell.c
File diff suppressed because it is too large
Load Diff
@@ -4,23 +4,41 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include "quickjs.h"
|
#include "quickjs.h"
|
||||||
#include "qjs_macros.h"
|
#include "qjs_macros.h"
|
||||||
|
#include "qjs_blob.h"
|
||||||
|
#include "blob.h"
|
||||||
|
|
||||||
#define STATE_VECTOR_LENGTH 624
|
#include <mimalloc.h>
|
||||||
#define STATE_VECTOR_M 397
|
|
||||||
|
|
||||||
#define ACTOR_IDLE 0
|
/* Letter type for unified message queue */
|
||||||
#define ACTOR_READY 1
|
typedef enum {
|
||||||
#define ACTOR_RUNNING 2
|
LETTER_BLOB, /* Blob message */
|
||||||
#define ACTOR_EXHAUSTED 3
|
LETTER_CALLBACK /* JSValue callback function */
|
||||||
#define ACTOR_RECLAIMING 4
|
} letter_type;
|
||||||
#define ACTOR_SLOW 5
|
|
||||||
|
typedef struct letter {
|
||||||
|
letter_type type;
|
||||||
|
union {
|
||||||
|
blob *blob_data; /* For LETTER_BLOB */
|
||||||
|
JSValue callback; /* For LETTER_CALLBACK */
|
||||||
|
};
|
||||||
|
} letter;
|
||||||
|
|
||||||
|
#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
|
||||||
|
#define ACTOR_RUNNING 2 // Actor taking a turn
|
||||||
|
#define ACTOR_EXHAUSTED 3 // Actor waiting for GC
|
||||||
|
#define ACTOR_RECLAIMING 4 // Actor running GC
|
||||||
|
#define ACTOR_SLOW 5 // Actor going slowly; deprioritize
|
||||||
|
|
||||||
typedef JSValue (*MODULEFN)(JSContext *js);
|
typedef JSValue (*MODULEFN)(JSContext *js);
|
||||||
|
|
||||||
extern int tracy_profiling_enabled;
|
extern int tracy_profiling_enabled;
|
||||||
|
|
||||||
typedef struct tagMTRand {
|
typedef struct tagMTRand {
|
||||||
uint32_t mt[STATE_VECTOR_LENGTH];
|
uint64_t mt[STATE_VECTOR_LENGTH];
|
||||||
int32_t index;
|
int32_t index;
|
||||||
} MTRand;
|
} MTRand;
|
||||||
|
|
||||||
@@ -31,75 +49,68 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct cell_rt {
|
typedef struct cell_rt {
|
||||||
JSContext *context;
|
JSContext *context;
|
||||||
JSValue cycle_fn;
|
mi_heap_t *heap;
|
||||||
JSValue idx_buffer;
|
JSValue idx_buffer;
|
||||||
JSValue on_exception;
|
JSValue on_exception;
|
||||||
JSValue message_handle;
|
JSValue message_handle;
|
||||||
JSValue unneeded;
|
|
||||||
|
|
||||||
void *init_wota;
|
void *init_wota;
|
||||||
|
|
||||||
ModuleEntry *module_registry;
|
ModuleEntry *module_registry;
|
||||||
JSValue *js_swapchains;
|
JSValue *js_swapchains;
|
||||||
|
|
||||||
/* Protects JSContext usage */
|
/* Protects JSContext usage */
|
||||||
SDL_Mutex *mutex;
|
SDL_Mutex *mutex; /* for everything else */
|
||||||
|
SDL_Mutex *msg_mutex; /* For message queue and timers queue */
|
||||||
|
|
||||||
SDL_Mutex *turn;
|
|
||||||
|
|
||||||
char *id;
|
char *id;
|
||||||
MTRand mrand;
|
MTRand mrand;
|
||||||
double unneeded_secs;
|
|
||||||
double ar_secs;
|
|
||||||
int idx_count;
|
int idx_count;
|
||||||
|
|
||||||
/* The “mailbox” for incoming messages + a dedicated lock for it: */
|
/* The “mailbox” for incoming messages + a dedicated lock for it: */
|
||||||
void **messages;
|
struct letter *letters;
|
||||||
JSValue *events;
|
|
||||||
SDL_Mutex *msg_mutex; /* For messages queue only */
|
|
||||||
|
|
||||||
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
|
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
|
||||||
struct { Uint32 key; JSValue value; } *timers;
|
struct { Uint32 key; JSValue value; } *timers;
|
||||||
|
|
||||||
int state;
|
int state;
|
||||||
Uint32 ar;
|
Uint32 ar; // timer for unneeded
|
||||||
int need_stop;
|
double ar_secs; // time for unneeded
|
||||||
|
JSValue unneeded; // fn to call before unneeded
|
||||||
|
|
||||||
int disrupt;
|
int disrupt;
|
||||||
int main_thread_only;
|
int main_thread_only;
|
||||||
|
int affinity;
|
||||||
|
|
||||||
JSAtom actor_sym;
|
JSAtom actor_sym;
|
||||||
JSAtom doc_sym;
|
|
||||||
|
|
||||||
const char *name; // human friendly name
|
const char *name; // human friendly name
|
||||||
} cell_rt;
|
} cell_rt;
|
||||||
|
|
||||||
extern SDL_ThreadID main_thread;
|
|
||||||
extern SDL_TLSID prosperon_id;
|
extern SDL_TLSID prosperon_id;
|
||||||
|
|
||||||
extern cell_rt *root_cell; // first actor in the system
|
extern cell_rt *root_cell; // first actor in the system
|
||||||
|
|
||||||
cell_rt *create_actor(void *wota, void (*hook)(JSContext*));
|
cell_rt *create_actor(void *wota);
|
||||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, JSValue config);
|
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||||
void actor_disrupt(cell_rt *actor);
|
void actor_disrupt(cell_rt *actor);
|
||||||
|
|
||||||
const char *send_message(const char *id, void *msg);
|
const char *send_message(const char *id, void *msg);
|
||||||
Uint32 actor_timer_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval);
|
Uint32 actor_timer_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval);
|
||||||
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv);
|
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv);
|
||||||
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv);
|
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv);
|
||||||
void script_startup(cell_rt *rt, void (*hook)(JSContext*));
|
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);
|
||||||
void script_evalf(JSContext *js, const char *format, ...);
|
void script_startup(cell_rt *rt);
|
||||||
JSValue script_eval(JSContext *js, const char *file, const char *script);
|
|
||||||
int uncaught_exception(JSContext *js, JSValue v);
|
int uncaught_exception(JSContext *js, JSValue v);
|
||||||
int actor_exists(const char *id);
|
int actor_exists(const char *id);
|
||||||
cell_rt *get_actor(char *id);
|
cell_rt *get_actor(char *id);
|
||||||
void set_actor_state(cell_rt *actor);
|
void set_actor_state(cell_rt *actor);
|
||||||
|
void actor_clock(cell_rt *actor, JSValue fn);
|
||||||
|
|
||||||
int JS_ArrayLength(JSContext *js, JSValue a);
|
int JS_ArrayLength(JSContext *js, JSValue a);
|
||||||
|
|
||||||
int prosperon_mount_core(void);
|
int prosperon_mount_core(void);
|
||||||
|
|
||||||
// Event watchers for SDL events
|
|
||||||
extern char **event_watchers;
|
|
||||||
extern SDL_Mutex *event_watchers_mutex;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
633
source/cutils.c
Normal file
633
source/cutils.c
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
/*
|
||||||
|
* C utilities
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 Fabrice Bellard
|
||||||
|
* Copyright (c) 2018 Charlie Gordon
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cutils.h"
|
||||||
|
|
||||||
|
void pstrcpy(char *buf, int buf_size, const char *str)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
char *q = buf;
|
||||||
|
|
||||||
|
if (buf_size <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
c = *str++;
|
||||||
|
if (c == 0 || q >= buf + buf_size - 1)
|
||||||
|
break;
|
||||||
|
*q++ = c;
|
||||||
|
}
|
||||||
|
*q = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* strcat and truncate. */
|
||||||
|
char *pstrcat(char *buf, int buf_size, const char *s)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
len = strlen(buf);
|
||||||
|
if (len < buf_size)
|
||||||
|
pstrcpy(buf + len, buf_size - len, s);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strstart(const char *str, const char *val, const char **ptr)
|
||||||
|
{
|
||||||
|
const char *p, *q;
|
||||||
|
p = str;
|
||||||
|
q = val;
|
||||||
|
while (*q != '\0') {
|
||||||
|
if (*p != *q)
|
||||||
|
return 0;
|
||||||
|
p++;
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
if (ptr)
|
||||||
|
*ptr = p;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int has_suffix(const char *str, const char *suffix)
|
||||||
|
{
|
||||||
|
size_t len = strlen(str);
|
||||||
|
size_t slen = strlen(suffix);
|
||||||
|
return (len >= slen && !memcmp(str + len - slen, suffix, slen));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dynamic buffer package */
|
||||||
|
|
||||||
|
static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size)
|
||||||
|
{
|
||||||
|
return realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func)
|
||||||
|
{
|
||||||
|
memset(s, 0, sizeof(*s));
|
||||||
|
if (!realloc_func)
|
||||||
|
realloc_func = dbuf_default_realloc;
|
||||||
|
s->opaque = opaque;
|
||||||
|
s->realloc_func = realloc_func;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dbuf_init(DynBuf *s)
|
||||||
|
{
|
||||||
|
dbuf_init2(s, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* return < 0 if error */
|
||||||
|
int dbuf_realloc(DynBuf *s, size_t new_size)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
uint8_t *new_buf;
|
||||||
|
if (new_size > s->allocated_size) {
|
||||||
|
if (s->error)
|
||||||
|
return -1;
|
||||||
|
size = s->allocated_size * 3 / 2;
|
||||||
|
if (size > new_size)
|
||||||
|
new_size = size;
|
||||||
|
new_buf = s->realloc_func(s->opaque, s->buf, new_size);
|
||||||
|
if (!new_buf) {
|
||||||
|
s->error = TRUE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->buf = new_buf;
|
||||||
|
s->allocated_size = new_size;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
size_t end;
|
||||||
|
end = offset + len;
|
||||||
|
if (dbuf_realloc(s, end))
|
||||||
|
return -1;
|
||||||
|
memcpy(s->buf + offset, data, len);
|
||||||
|
if (end > s->size)
|
||||||
|
s->size = end;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbuf_put(DynBuf *s, const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (unlikely((s->size + len) > s->allocated_size)) {
|
||||||
|
if (dbuf_realloc(s, s->size + len))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy_no_ub(s->buf + s->size, data, len);
|
||||||
|
s->size += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbuf_put_self(DynBuf *s, size_t offset, size_t len)
|
||||||
|
{
|
||||||
|
if (unlikely((s->size + len) > s->allocated_size)) {
|
||||||
|
if (dbuf_realloc(s, s->size + len))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(s->buf + s->size, s->buf + offset, len);
|
||||||
|
s->size += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbuf_putc(DynBuf *s, uint8_t c)
|
||||||
|
{
|
||||||
|
return dbuf_put(s, &c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbuf_putstr(DynBuf *s, const char *str)
|
||||||
|
{
|
||||||
|
return dbuf_put(s, (const uint8_t *)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s,
|
||||||
|
const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
char buf[128];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
len = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
if (len < 0)
|
||||||
|
return -1;
|
||||||
|
if (len < sizeof(buf)) {
|
||||||
|
/* fast case */
|
||||||
|
return dbuf_put(s, (uint8_t *)buf, len);
|
||||||
|
} else {
|
||||||
|
if (dbuf_realloc(s, s->size + len + 1))
|
||||||
|
return -1;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size,
|
||||||
|
fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
s->size += len;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dbuf_free(DynBuf *s)
|
||||||
|
{
|
||||||
|
/* we test s->buf as a fail safe to avoid crashing if dbuf_free()
|
||||||
|
is called twice */
|
||||||
|
if (s->buf) {
|
||||||
|
s->realloc_func(s->opaque, s->buf, 0);
|
||||||
|
}
|
||||||
|
memset(s, 0, sizeof(*s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note: at most 31 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes
|
||||||
|
are output. */
|
||||||
|
int unicode_to_utf8(uint8_t *buf, unsigned int c)
|
||||||
|
{
|
||||||
|
uint8_t *q = buf;
|
||||||
|
|
||||||
|
if (c < 0x80) {
|
||||||
|
*q++ = c;
|
||||||
|
} else {
|
||||||
|
if (c < 0x800) {
|
||||||
|
*q++ = (c >> 6) | 0xc0;
|
||||||
|
} else {
|
||||||
|
if (c < 0x10000) {
|
||||||
|
*q++ = (c >> 12) | 0xe0;
|
||||||
|
} else {
|
||||||
|
if (c < 0x00200000) {
|
||||||
|
*q++ = (c >> 18) | 0xf0;
|
||||||
|
} else {
|
||||||
|
if (c < 0x04000000) {
|
||||||
|
*q++ = (c >> 24) | 0xf8;
|
||||||
|
} else if (c < 0x80000000) {
|
||||||
|
*q++ = (c >> 30) | 0xfc;
|
||||||
|
*q++ = ((c >> 24) & 0x3f) | 0x80;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*q++ = ((c >> 18) & 0x3f) | 0x80;
|
||||||
|
}
|
||||||
|
*q++ = ((c >> 12) & 0x3f) | 0x80;
|
||||||
|
}
|
||||||
|
*q++ = ((c >> 6) & 0x3f) | 0x80;
|
||||||
|
}
|
||||||
|
*q++ = (c & 0x3f) | 0x80;
|
||||||
|
}
|
||||||
|
return q - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned int utf8_min_code[5] = {
|
||||||
|
0x80, 0x800, 0x10000, 0x00200000, 0x04000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char utf8_first_code_mask[5] = {
|
||||||
|
0x1f, 0xf, 0x7, 0x3, 0x1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* return -1 if error. *pp is not updated in this case. max_len must
|
||||||
|
be >= 1. The maximum length for a UTF8 byte sequence is 6 bytes. */
|
||||||
|
int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp)
|
||||||
|
{
|
||||||
|
int l, c, b, i;
|
||||||
|
|
||||||
|
c = *p++;
|
||||||
|
if (c < 0x80) {
|
||||||
|
*pp = p;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
switch(c) {
|
||||||
|
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||||
|
case 0xc4: case 0xc5: case 0xc6: case 0xc7:
|
||||||
|
case 0xc8: case 0xc9: case 0xca: case 0xcb:
|
||||||
|
case 0xcc: case 0xcd: case 0xce: case 0xcf:
|
||||||
|
case 0xd0: case 0xd1: case 0xd2: case 0xd3:
|
||||||
|
case 0xd4: case 0xd5: case 0xd6: case 0xd7:
|
||||||
|
case 0xd8: case 0xd9: case 0xda: case 0xdb:
|
||||||
|
case 0xdc: case 0xdd: case 0xde: case 0xdf:
|
||||||
|
l = 1;
|
||||||
|
break;
|
||||||
|
case 0xe0: case 0xe1: case 0xe2: case 0xe3:
|
||||||
|
case 0xe4: case 0xe5: case 0xe6: case 0xe7:
|
||||||
|
case 0xe8: case 0xe9: case 0xea: case 0xeb:
|
||||||
|
case 0xec: case 0xed: case 0xee: case 0xef:
|
||||||
|
l = 2;
|
||||||
|
break;
|
||||||
|
case 0xf0: case 0xf1: case 0xf2: case 0xf3:
|
||||||
|
case 0xf4: case 0xf5: case 0xf6: case 0xf7:
|
||||||
|
l = 3;
|
||||||
|
break;
|
||||||
|
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
||||||
|
l = 4;
|
||||||
|
break;
|
||||||
|
case 0xfc: case 0xfd:
|
||||||
|
l = 5;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* check that we have enough characters */
|
||||||
|
if (l > (max_len - 1))
|
||||||
|
return -1;
|
||||||
|
c &= utf8_first_code_mask[l - 1];
|
||||||
|
for(i = 0; i < l; i++) {
|
||||||
|
b = *p++;
|
||||||
|
if (b < 0x80 || b >= 0xc0)
|
||||||
|
return -1;
|
||||||
|
c = (c << 6) | (b & 0x3f);
|
||||||
|
}
|
||||||
|
if (c < utf8_min_code[l - 1])
|
||||||
|
return -1;
|
||||||
|
*pp = p;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
#if defined(EMSCRIPTEN) || defined(__ANDROID__)
|
||||||
|
|
||||||
|
static void *rqsort_arg;
|
||||||
|
static int (*rqsort_cmp)(const void *, const void *, void *);
|
||||||
|
|
||||||
|
static int rqsort_cmp2(const void *p1, const void *p2)
|
||||||
|
{
|
||||||
|
return rqsort_cmp(p1, p2, rqsort_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* not reentrant, but not needed with emscripten */
|
||||||
|
void rqsort(void *base, size_t nmemb, size_t size,
|
||||||
|
int (*cmp)(const void *, const void *, void *),
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
rqsort_arg = arg;
|
||||||
|
rqsort_cmp = cmp;
|
||||||
|
qsort(base, nmemb, size, rqsort_cmp2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
typedef void (*exchange_f)(void *a, void *b, size_t size);
|
||||||
|
typedef int (*cmp_f)(const void *, const void *, void *opaque);
|
||||||
|
|
||||||
|
static void exchange_bytes(void *a, void *b, size_t size) {
|
||||||
|
uint8_t *ap = (uint8_t *)a;
|
||||||
|
uint8_t *bp = (uint8_t *)b;
|
||||||
|
|
||||||
|
while (size-- != 0) {
|
||||||
|
uint8_t t = *ap;
|
||||||
|
*ap++ = *bp;
|
||||||
|
*bp++ = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_one_byte(void *a, void *b, size_t size) {
|
||||||
|
uint8_t *ap = (uint8_t *)a;
|
||||||
|
uint8_t *bp = (uint8_t *)b;
|
||||||
|
uint8_t t = *ap;
|
||||||
|
*ap = *bp;
|
||||||
|
*bp = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_int16s(void *a, void *b, size_t size) {
|
||||||
|
uint16_t *ap = (uint16_t *)a;
|
||||||
|
uint16_t *bp = (uint16_t *)b;
|
||||||
|
|
||||||
|
for (size /= sizeof(uint16_t); size-- != 0;) {
|
||||||
|
uint16_t t = *ap;
|
||||||
|
*ap++ = *bp;
|
||||||
|
*bp++ = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_one_int16(void *a, void *b, size_t size) {
|
||||||
|
uint16_t *ap = (uint16_t *)a;
|
||||||
|
uint16_t *bp = (uint16_t *)b;
|
||||||
|
uint16_t t = *ap;
|
||||||
|
*ap = *bp;
|
||||||
|
*bp = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_int32s(void *a, void *b, size_t size) {
|
||||||
|
uint32_t *ap = (uint32_t *)a;
|
||||||
|
uint32_t *bp = (uint32_t *)b;
|
||||||
|
|
||||||
|
for (size /= sizeof(uint32_t); size-- != 0;) {
|
||||||
|
uint32_t t = *ap;
|
||||||
|
*ap++ = *bp;
|
||||||
|
*bp++ = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_one_int32(void *a, void *b, size_t size) {
|
||||||
|
uint32_t *ap = (uint32_t *)a;
|
||||||
|
uint32_t *bp = (uint32_t *)b;
|
||||||
|
uint32_t t = *ap;
|
||||||
|
*ap = *bp;
|
||||||
|
*bp = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_int64s(void *a, void *b, size_t size) {
|
||||||
|
uint64_t *ap = (uint64_t *)a;
|
||||||
|
uint64_t *bp = (uint64_t *)b;
|
||||||
|
|
||||||
|
for (size /= sizeof(uint64_t); size-- != 0;) {
|
||||||
|
uint64_t t = *ap;
|
||||||
|
*ap++ = *bp;
|
||||||
|
*bp++ = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_one_int64(void *a, void *b, size_t size) {
|
||||||
|
uint64_t *ap = (uint64_t *)a;
|
||||||
|
uint64_t *bp = (uint64_t *)b;
|
||||||
|
uint64_t t = *ap;
|
||||||
|
*ap = *bp;
|
||||||
|
*bp = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_int128s(void *a, void *b, size_t size) {
|
||||||
|
uint64_t *ap = (uint64_t *)a;
|
||||||
|
uint64_t *bp = (uint64_t *)b;
|
||||||
|
|
||||||
|
for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) {
|
||||||
|
uint64_t t = ap[0];
|
||||||
|
uint64_t u = ap[1];
|
||||||
|
ap[0] = bp[0];
|
||||||
|
ap[1] = bp[1];
|
||||||
|
bp[0] = t;
|
||||||
|
bp[1] = u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void exchange_one_int128(void *a, void *b, size_t size) {
|
||||||
|
uint64_t *ap = (uint64_t *)a;
|
||||||
|
uint64_t *bp = (uint64_t *)b;
|
||||||
|
uint64_t t = ap[0];
|
||||||
|
uint64_t u = ap[1];
|
||||||
|
ap[0] = bp[0];
|
||||||
|
ap[1] = bp[1];
|
||||||
|
bp[0] = t;
|
||||||
|
bp[1] = u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline exchange_f exchange_func(const void *base, size_t size) {
|
||||||
|
switch (((uintptr_t)base | (uintptr_t)size) & 15) {
|
||||||
|
case 0:
|
||||||
|
if (size == sizeof(uint64_t) * 2)
|
||||||
|
return exchange_one_int128;
|
||||||
|
else
|
||||||
|
return exchange_int128s;
|
||||||
|
case 8:
|
||||||
|
if (size == sizeof(uint64_t))
|
||||||
|
return exchange_one_int64;
|
||||||
|
else
|
||||||
|
return exchange_int64s;
|
||||||
|
case 4:
|
||||||
|
case 12:
|
||||||
|
if (size == sizeof(uint32_t))
|
||||||
|
return exchange_one_int32;
|
||||||
|
else
|
||||||
|
return exchange_int32s;
|
||||||
|
case 2:
|
||||||
|
case 6:
|
||||||
|
case 10:
|
||||||
|
case 14:
|
||||||
|
if (size == sizeof(uint16_t))
|
||||||
|
return exchange_one_int16;
|
||||||
|
else
|
||||||
|
return exchange_int16s;
|
||||||
|
default:
|
||||||
|
if (size == 1)
|
||||||
|
return exchange_one_byte;
|
||||||
|
else
|
||||||
|
return exchange_bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque)
|
||||||
|
{
|
||||||
|
uint8_t *basep = (uint8_t *)base;
|
||||||
|
size_t i, n, c, r;
|
||||||
|
exchange_f swap = exchange_func(base, size);
|
||||||
|
|
||||||
|
if (nmemb > 1) {
|
||||||
|
i = (nmemb / 2) * size;
|
||||||
|
n = nmemb * size;
|
||||||
|
|
||||||
|
while (i > 0) {
|
||||||
|
i -= size;
|
||||||
|
for (r = i; (c = r * 2 + size) < n; r = c) {
|
||||||
|
if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0)
|
||||||
|
c += size;
|
||||||
|
if (cmp(basep + r, basep + c, opaque) > 0)
|
||||||
|
break;
|
||||||
|
swap(basep + r, basep + c, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = n - size; i > 0; i -= size) {
|
||||||
|
swap(basep, basep + i, size);
|
||||||
|
|
||||||
|
for (r = 0; (c = r * 2 + size) < i; r = c) {
|
||||||
|
if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0)
|
||||||
|
c += size;
|
||||||
|
if (cmp(basep + r, basep + c, opaque) > 0)
|
||||||
|
break;
|
||||||
|
swap(basep + r, basep + c, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque)
|
||||||
|
{
|
||||||
|
return cmp(a, b, opaque) < 0 ?
|
||||||
|
(cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) :
|
||||||
|
(cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c ));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pointer based version with local stack and insertion sort threshhold */
|
||||||
|
void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque)
|
||||||
|
{
|
||||||
|
struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack;
|
||||||
|
uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m;
|
||||||
|
size_t m4, i, lt, gt, span, span2;
|
||||||
|
int c, depth;
|
||||||
|
exchange_f swap = exchange_func(base, size);
|
||||||
|
exchange_f swap_block = exchange_func(base, size | 128);
|
||||||
|
|
||||||
|
if (nmemb < 2 || size <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sp->base = (uint8_t *)base;
|
||||||
|
sp->count = nmemb;
|
||||||
|
sp->depth = 0;
|
||||||
|
sp++;
|
||||||
|
|
||||||
|
while (sp > stack) {
|
||||||
|
sp--;
|
||||||
|
ptr = sp->base;
|
||||||
|
nmemb = sp->count;
|
||||||
|
depth = sp->depth;
|
||||||
|
|
||||||
|
while (nmemb > 6) {
|
||||||
|
if (++depth > 50) {
|
||||||
|
/* depth check to ensure worst case logarithmic time */
|
||||||
|
heapsortx(ptr, nmemb, size, cmp, opaque);
|
||||||
|
nmemb = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* select median of 3 from 1/4, 1/2, 3/4 positions */
|
||||||
|
/* should use median of 5 or 9? */
|
||||||
|
m4 = (nmemb >> 2) * size;
|
||||||
|
m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque);
|
||||||
|
swap(ptr, m, size); /* move the pivot to the start or the array */
|
||||||
|
i = lt = 1;
|
||||||
|
pi = plt = ptr + size;
|
||||||
|
gt = nmemb;
|
||||||
|
pj = pgt = top = ptr + nmemb * size;
|
||||||
|
for (;;) {
|
||||||
|
while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) {
|
||||||
|
if (c == 0) {
|
||||||
|
swap(plt, pi, size);
|
||||||
|
lt++;
|
||||||
|
plt += size;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
pi += size;
|
||||||
|
}
|
||||||
|
while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) {
|
||||||
|
if (c == 0) {
|
||||||
|
gt--;
|
||||||
|
pgt -= size;
|
||||||
|
swap(pgt, pj, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pi >= pj)
|
||||||
|
break;
|
||||||
|
swap(pi, pj, size);
|
||||||
|
i++;
|
||||||
|
pi += size;
|
||||||
|
}
|
||||||
|
/* array has 4 parts:
|
||||||
|
* from 0 to lt excluded: elements identical to pivot
|
||||||
|
* from lt to pi excluded: elements smaller than pivot
|
||||||
|
* from pi to gt excluded: elements greater than pivot
|
||||||
|
* from gt to n excluded: elements identical to pivot
|
||||||
|
*/
|
||||||
|
/* move elements identical to pivot in the middle of the array: */
|
||||||
|
/* swap values in ranges [0..lt[ and [i-lt..i[
|
||||||
|
swapping the smallest span between lt and i-lt is sufficient
|
||||||
|
*/
|
||||||
|
span = plt - ptr;
|
||||||
|
span2 = pi - plt;
|
||||||
|
lt = i - lt;
|
||||||
|
if (span > span2)
|
||||||
|
span = span2;
|
||||||
|
swap_block(ptr, pi - span, span);
|
||||||
|
/* swap values in ranges [gt..top[ and [i..top-(top-gt)[
|
||||||
|
swapping the smallest span between top-gt and gt-i is sufficient
|
||||||
|
*/
|
||||||
|
span = top - pgt;
|
||||||
|
span2 = pgt - pi;
|
||||||
|
pgt = top - span2;
|
||||||
|
gt = nmemb - (gt - i);
|
||||||
|
if (span > span2)
|
||||||
|
span = span2;
|
||||||
|
swap_block(pi, top - span, span);
|
||||||
|
|
||||||
|
/* now array has 3 parts:
|
||||||
|
* from 0 to lt excluded: elements smaller than pivot
|
||||||
|
* from lt to gt excluded: elements identical to pivot
|
||||||
|
* from gt to n excluded: elements greater than pivot
|
||||||
|
*/
|
||||||
|
/* stack the larger segment and keep processing the smaller one
|
||||||
|
to minimize stack use for pathological distributions */
|
||||||
|
if (lt > nmemb - gt) {
|
||||||
|
sp->base = ptr;
|
||||||
|
sp->count = lt;
|
||||||
|
sp->depth = depth;
|
||||||
|
sp++;
|
||||||
|
ptr = pgt;
|
||||||
|
nmemb -= gt;
|
||||||
|
} else {
|
||||||
|
sp->base = pgt;
|
||||||
|
sp->count = nmemb - gt;
|
||||||
|
sp->depth = depth;
|
||||||
|
sp++;
|
||||||
|
nmemb = lt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Use insertion sort for small fragments */
|
||||||
|
for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) {
|
||||||
|
for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size)
|
||||||
|
swap(pj, pj - size, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
423
source/cutils.h
Normal file
423
source/cutils.h
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
/*
|
||||||
|
* C utilities
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 Fabrice Bellard
|
||||||
|
* Copyright (c) 2018 Charlie Gordon
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef CUTILS_H
|
||||||
|
#define CUTILS_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
#define force_inline inline __attribute__((always_inline))
|
||||||
|
#define no_inline __attribute__((noinline))
|
||||||
|
#define __maybe_unused __attribute__((unused))
|
||||||
|
|
||||||
|
#define xglue(x, y) x ## y
|
||||||
|
#define glue(x, y) xglue(x, y)
|
||||||
|
#define stringify(s) tostring(s)
|
||||||
|
#define tostring(s) #s
|
||||||
|
|
||||||
|
#ifndef offsetof
|
||||||
|
#define offsetof(type, field) ((size_t) &((type *)0)->field)
|
||||||
|
#endif
|
||||||
|
#ifndef countof
|
||||||
|
#define countof(x) (sizeof(x) / sizeof((x)[0]))
|
||||||
|
#endif
|
||||||
|
#ifndef container_of
|
||||||
|
/* return the pointer of type 'type *' containing 'ptr' as field 'member' */
|
||||||
|
#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||||
|
#define minimum_length(n) static n
|
||||||
|
#else
|
||||||
|
#define minimum_length(n) n
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef int BOOL;
|
||||||
|
|
||||||
|
#ifndef FALSE
|
||||||
|
enum {
|
||||||
|
FALSE = 0,
|
||||||
|
TRUE = 1,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void pstrcpy(char *buf, int buf_size, const char *str);
|
||||||
|
char *pstrcat(char *buf, int buf_size, const char *s);
|
||||||
|
int strstart(const char *str, const char *val, const char **ptr);
|
||||||
|
int has_suffix(const char *str, const char *suffix);
|
||||||
|
|
||||||
|
/* Prevent UB when n == 0 and (src == NULL or dest == NULL) */
|
||||||
|
static inline void memcpy_no_ub(void *dest, const void *src, size_t n) {
|
||||||
|
if (n)
|
||||||
|
memcpy(dest, src, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int max_int(int a, int b)
|
||||||
|
{
|
||||||
|
if (a > b)
|
||||||
|
return a;
|
||||||
|
else
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int min_int(int a, int b)
|
||||||
|
{
|
||||||
|
if (a < b)
|
||||||
|
return a;
|
||||||
|
else
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t max_uint32(uint32_t a, uint32_t b)
|
||||||
|
{
|
||||||
|
if (a > b)
|
||||||
|
return a;
|
||||||
|
else
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t min_uint32(uint32_t a, uint32_t b)
|
||||||
|
{
|
||||||
|
if (a < b)
|
||||||
|
return a;
|
||||||
|
else
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int64_t max_int64(int64_t a, int64_t b)
|
||||||
|
{
|
||||||
|
if (a > b)
|
||||||
|
return a;
|
||||||
|
else
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int64_t min_int64(int64_t a, int64_t b)
|
||||||
|
{
|
||||||
|
if (a < b)
|
||||||
|
return a;
|
||||||
|
else
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WARNING: undefined if a = 0 */
|
||||||
|
static inline int clz32(unsigned int a)
|
||||||
|
{
|
||||||
|
return __builtin_clz(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WARNING: undefined if a = 0 */
|
||||||
|
static inline int clz64(uint64_t a)
|
||||||
|
{
|
||||||
|
return __builtin_clzll(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WARNING: undefined if a = 0 */
|
||||||
|
static inline int ctz32(unsigned int a)
|
||||||
|
{
|
||||||
|
return __builtin_ctz(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WARNING: undefined if a = 0 */
|
||||||
|
static inline int ctz64(uint64_t a)
|
||||||
|
{
|
||||||
|
return __builtin_ctzll(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct __attribute__((packed)) packed_u64 {
|
||||||
|
uint64_t v;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __attribute__((packed)) packed_u32 {
|
||||||
|
uint32_t v;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __attribute__((packed)) packed_u16 {
|
||||||
|
uint16_t v;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline uint64_t get_u64(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return ((const struct packed_u64 *)tab)->v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int64_t get_i64(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return (int64_t)((const struct packed_u64 *)tab)->v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void put_u64(uint8_t *tab, uint64_t val)
|
||||||
|
{
|
||||||
|
((struct packed_u64 *)tab)->v = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t get_u32(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return ((const struct packed_u32 *)tab)->v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t get_i32(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return (int32_t)((const struct packed_u32 *)tab)->v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void put_u32(uint8_t *tab, uint32_t val)
|
||||||
|
{
|
||||||
|
((struct packed_u32 *)tab)->v = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t get_u16(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return ((const struct packed_u16 *)tab)->v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t get_i16(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return (int16_t)((const struct packed_u16 *)tab)->v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void put_u16(uint8_t *tab, uint16_t val)
|
||||||
|
{
|
||||||
|
((struct packed_u16 *)tab)->v = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t get_u8(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return *tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t get_i8(const uint8_t *tab)
|
||||||
|
{
|
||||||
|
return (int8_t)*tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void put_u8(uint8_t *tab, uint8_t val)
|
||||||
|
{
|
||||||
|
*tab = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef bswap16
|
||||||
|
static inline uint16_t bswap16(uint16_t x)
|
||||||
|
{
|
||||||
|
return (x >> 8) | (x << 8);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef bswap32
|
||||||
|
static inline uint32_t bswap32(uint32_t v)
|
||||||
|
{
|
||||||
|
return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) |
|
||||||
|
((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef bswap64
|
||||||
|
static inline uint64_t bswap64(uint64_t v)
|
||||||
|
{
|
||||||
|
return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) |
|
||||||
|
((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* XXX: should take an extra argument to pass slack information to the caller */
|
||||||
|
typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size);
|
||||||
|
|
||||||
|
typedef struct DynBuf {
|
||||||
|
uint8_t *buf;
|
||||||
|
size_t size;
|
||||||
|
size_t allocated_size;
|
||||||
|
BOOL error; /* true if a memory allocation error occurred */
|
||||||
|
DynBufReallocFunc *realloc_func;
|
||||||
|
void *opaque; /* for realloc_func */
|
||||||
|
} DynBuf;
|
||||||
|
|
||||||
|
void dbuf_init(DynBuf *s);
|
||||||
|
void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func);
|
||||||
|
int dbuf_realloc(DynBuf *s, size_t new_size);
|
||||||
|
int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len);
|
||||||
|
int dbuf_put(DynBuf *s, const uint8_t *data, size_t len);
|
||||||
|
int dbuf_put_self(DynBuf *s, size_t offset, size_t len);
|
||||||
|
int dbuf_putc(DynBuf *s, uint8_t c);
|
||||||
|
int dbuf_putstr(DynBuf *s, const char *str);
|
||||||
|
static inline int dbuf_put_u16(DynBuf *s, uint16_t val)
|
||||||
|
{
|
||||||
|
return dbuf_put(s, (uint8_t *)&val, 2);
|
||||||
|
}
|
||||||
|
static inline int dbuf_put_u32(DynBuf *s, uint32_t val)
|
||||||
|
{
|
||||||
|
return dbuf_put(s, (uint8_t *)&val, 4);
|
||||||
|
}
|
||||||
|
static inline int dbuf_put_u64(DynBuf *s, uint64_t val)
|
||||||
|
{
|
||||||
|
return dbuf_put(s, (uint8_t *)&val, 8);
|
||||||
|
}
|
||||||
|
int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s,
|
||||||
|
const char *fmt, ...);
|
||||||
|
void dbuf_free(DynBuf *s);
|
||||||
|
static inline BOOL dbuf_error(DynBuf *s) {
|
||||||
|
return s->error;
|
||||||
|
}
|
||||||
|
static inline void dbuf_set_error(DynBuf *s)
|
||||||
|
{
|
||||||
|
s->error = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UTF8_CHAR_LEN_MAX 6
|
||||||
|
|
||||||
|
int unicode_to_utf8(uint8_t *buf, unsigned int c);
|
||||||
|
int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp);
|
||||||
|
|
||||||
|
static inline BOOL is_surrogate(uint32_t c)
|
||||||
|
{
|
||||||
|
return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline BOOL is_hi_surrogate(uint32_t c)
|
||||||
|
{
|
||||||
|
return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline BOOL is_lo_surrogate(uint32_t c)
|
||||||
|
{
|
||||||
|
return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t get_hi_surrogate(uint32_t c)
|
||||||
|
{
|
||||||
|
return (c >> 10) - (0x10000 >> 10) + 0xD800;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t get_lo_surrogate(uint32_t c)
|
||||||
|
{
|
||||||
|
return (c & 0x3FF) | 0xDC00;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo)
|
||||||
|
{
|
||||||
|
return 0x10000 + 0x400 * (hi - 0xD800) + (lo - 0xDC00);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int from_hex(int c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
return c - 'A' + 10;
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
return c - 'a' + 10;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rqsort(void *base, size_t nmemb, size_t size,
|
||||||
|
int (*cmp)(const void *, const void *, void *),
|
||||||
|
void *arg);
|
||||||
|
|
||||||
|
static inline uint64_t float64_as_uint64(double d)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
double d;
|
||||||
|
uint64_t u64;
|
||||||
|
} u;
|
||||||
|
u.d = d;
|
||||||
|
return u.u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline double uint64_as_float64(uint64_t u64)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
double d;
|
||||||
|
uint64_t u64;
|
||||||
|
} u;
|
||||||
|
u.u64 = u64;
|
||||||
|
return u.d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline double fromfp16(uint16_t v)
|
||||||
|
{
|
||||||
|
double d;
|
||||||
|
uint32_t v1;
|
||||||
|
v1 = v & 0x7fff;
|
||||||
|
if (unlikely(v1 >= 0x7c00))
|
||||||
|
v1 += 0x1f8000; /* NaN or infinity */
|
||||||
|
d = uint64_as_float64(((uint64_t)(v >> 15) << 63) | ((uint64_t)v1 << (52 - 10)));
|
||||||
|
return d * 0x1p1008;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t tofp16(double d)
|
||||||
|
{
|
||||||
|
uint64_t a, addend;
|
||||||
|
uint32_t v, sgn;
|
||||||
|
int shift;
|
||||||
|
|
||||||
|
a = float64_as_uint64(d);
|
||||||
|
sgn = a >> 63;
|
||||||
|
a = a & 0x7fffffffffffffff;
|
||||||
|
if (unlikely(a > 0x7ff0000000000000)) {
|
||||||
|
/* nan */
|
||||||
|
v = 0x7c01;
|
||||||
|
} else if (a < 0x3f10000000000000) { /* 0x1p-14 */
|
||||||
|
/* subnormal f16 number or zero */
|
||||||
|
if (a <= 0x3e60000000000000) { /* 0x1p-25 */
|
||||||
|
v = 0x0000; /* zero */
|
||||||
|
} else {
|
||||||
|
shift = 1051 - (a >> 52);
|
||||||
|
a = ((uint64_t)1 << 52) | (a & (((uint64_t)1 << 52) - 1));
|
||||||
|
addend = ((a >> shift) & 1) + (((uint64_t)1 << (shift - 1)) - 1);
|
||||||
|
v = (a + addend) >> shift;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* normal number or infinity */
|
||||||
|
a -= 0x3f00000000000000; /* adjust the exponent */
|
||||||
|
/* round */
|
||||||
|
addend = ((a >> (52 - 10)) & 1) + (((uint64_t)1 << (52 - 11)) - 1);
|
||||||
|
v = (a + addend) >> (52 - 10);
|
||||||
|
/* overflow ? */
|
||||||
|
if (unlikely(v > 0x7c00))
|
||||||
|
v = 0x7c00;
|
||||||
|
}
|
||||||
|
return v | (sgn << 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int isfp16nan(uint16_t v)
|
||||||
|
{
|
||||||
|
return (v & 0x7FFF) > 0x7C00;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int isfp16zero(uint16_t v)
|
||||||
|
{
|
||||||
|
return (v & 0x7FFF) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CUTILS_H */
|
||||||
1620
source/dtoa.c
Normal file
1620
source/dtoa.c
Normal file
File diff suppressed because it is too large
Load Diff
83
source/dtoa.h
Normal file
83
source/dtoa.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Tiny float64 printing and parsing library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Fabrice Bellard
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#define JS_DTOA_DUMP_STATS
|
||||||
|
|
||||||
|
/* maximum number of digits for fixed and frac formats */
|
||||||
|
#define JS_DTOA_MAX_DIGITS 101
|
||||||
|
|
||||||
|
/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */
|
||||||
|
/* use as many digits as necessary */
|
||||||
|
#define JS_DTOA_FORMAT_FREE (0 << 0)
|
||||||
|
/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */
|
||||||
|
#define JS_DTOA_FORMAT_FIXED (1 << 0)
|
||||||
|
/* force fractional format: [-]dd.dd with n_digits fractional digits.
|
||||||
|
0 <= n_digits <= JS_DTOA_MAX_DIGITS */
|
||||||
|
#define JS_DTOA_FORMAT_FRAC (2 << 0)
|
||||||
|
#define JS_DTOA_FORMAT_MASK (3 << 0)
|
||||||
|
|
||||||
|
/* select exponential notation either in fixed or free format */
|
||||||
|
#define JS_DTOA_EXP_AUTO (0 << 2)
|
||||||
|
#define JS_DTOA_EXP_ENABLED (1 << 2)
|
||||||
|
#define JS_DTOA_EXP_DISABLED (2 << 2)
|
||||||
|
#define JS_DTOA_EXP_MASK (3 << 2)
|
||||||
|
|
||||||
|
#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */
|
||||||
|
|
||||||
|
/* only accepts integers (no dot, no exponent) */
|
||||||
|
#define JS_ATOD_INT_ONLY (1 << 0)
|
||||||
|
/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
|
||||||
|
#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1)
|
||||||
|
/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
|
||||||
|
#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2)
|
||||||
|
/* accept _ between digits as a digit separator */
|
||||||
|
#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t mem[37];
|
||||||
|
} JSDTOATempMem;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t mem[27];
|
||||||
|
} JSATODTempMem;
|
||||||
|
|
||||||
|
/* return a maximum bound of the string length */
|
||||||
|
int js_dtoa_max_len(double d, int radix, int n_digits, int flags);
|
||||||
|
/* return the string length */
|
||||||
|
int js_dtoa(char *buf, double d, int radix, int n_digits, int flags,
|
||||||
|
JSDTOATempMem *tmp_mem);
|
||||||
|
double js_atod(const char *str, const char **pnext, int radix, int flags,
|
||||||
|
JSATODTempMem *tmp_mem);
|
||||||
|
|
||||||
|
#ifdef JS_DTOA_DUMP_STATS
|
||||||
|
void js_dtoa_dump_stats(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* additional exported functions */
|
||||||
|
size_t u32toa(char *buf, uint32_t n);
|
||||||
|
size_t i32toa(char *buf, int32_t n);
|
||||||
|
size_t u64toa(char *buf, uint64_t n);
|
||||||
|
size_t i64toa(char *buf, int64_t n);
|
||||||
|
size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix);
|
||||||
|
size_t i64toa_radix(char *buf, int64_t n, unsigned int radix);
|
||||||
292
source/jsffi.c
292
source/jsffi.c
@@ -2,6 +2,7 @@
|
|||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "datastream.h"
|
#include "datastream.h"
|
||||||
#include "qjs_sdl.h"
|
#include "qjs_sdl.h"
|
||||||
|
#include "qjs_sdl_input.h"
|
||||||
#include "qjs_io.h"
|
#include "qjs_io.h"
|
||||||
#include "qjs_fd.h"
|
#include "qjs_fd.h"
|
||||||
#include "transform.h"
|
#include "transform.h"
|
||||||
@@ -52,6 +53,10 @@
|
|||||||
#include "qjs_debug.h"
|
#include "qjs_debug.h"
|
||||||
#include "qjs_sdl_surface.h"
|
#include "qjs_sdl_surface.h"
|
||||||
#include "qjs_sdl.h"
|
#include "qjs_sdl.h"
|
||||||
|
#include "qjs_kim.h"
|
||||||
|
#include "qjs_utf8.h"
|
||||||
|
#include "qjs_fit.h"
|
||||||
|
#include "qjs_text.h"
|
||||||
#ifndef NSTEAM
|
#ifndef NSTEAM
|
||||||
#include "qjs_steam.h"
|
#include "qjs_steam.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -98,55 +103,59 @@ transform *js2transform(JSContext *js, JSValue v);
|
|||||||
//#include <cblas.h>
|
//#include <cblas.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Random number generation constants
|
// Random number generation constants for MT19937-64
|
||||||
#define UPPER_MASK 0x80000000
|
#define NN STATE_VECTOR_LENGTH
|
||||||
#define LOWER_MASK 0x7fffffff
|
#define MM STATE_VECTOR_M
|
||||||
#define TEMPERING_MASK_B 0x9d2c5680
|
#define MATRIX_A 0xB5026F5AA96619E9ULL
|
||||||
#define TEMPERING_MASK_C 0xefc60000
|
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
|
||||||
|
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */
|
||||||
|
|
||||||
// Random number generation functions
|
// Random number generation functions
|
||||||
void m_seedRand(MTRand* rand, uint32_t seed) {
|
void m_seedRand(MTRand* rand, uint64_t seed) {
|
||||||
/* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator
|
rand->mt[0] = seed;
|
||||||
* from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer
|
for(rand->index = 1; rand->index < NN; rand->index++) {
|
||||||
* Programming," Vol. 2 (2nd Ed.) pp.102.
|
rand->mt[rand->index] = (6364136223846793005ULL * (rand->mt[rand->index-1] ^ (rand->mt[rand->index-1] >> 62)) + rand->index);
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t genRandLong(MTRand* rand) {
|
int64_t genRandLong(MTRand* rand) {
|
||||||
uint32_t y;
|
int i;
|
||||||
static uint32_t mag[2] = {0x0, 0x9908b0df}; /* mag[x] = x * 0x9908b0df for x = 0,1 */
|
uint64_t x;
|
||||||
if(rand->index >= STATE_VECTOR_LENGTH || rand->index < 0) {
|
static uint64_t mag01[2] = {0ULL, MATRIX_A};
|
||||||
/* generate STATE_VECTOR_LENGTH words at a time */
|
|
||||||
int32_t kk;
|
if (rand->index >= NN) { /* generate NN words at one time */
|
||||||
if(rand->index >= STATE_VECTOR_LENGTH+1 || rand->index < 0) {
|
/* if init_genrand64() has not been called, */
|
||||||
m_seedRand(rand, 4357);
|
/* 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++) {
|
for (; i < NN-1; i++) {
|
||||||
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
|
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
|
||||||
rand->mt[kk] = rand->mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1];
|
rand->mt[i] = rand->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||||
}
|
}
|
||||||
for(; kk<STATE_VECTOR_LENGTH-1; kk++) {
|
x = (rand->mt[NN-1] & UM) | (rand->mt[0] & LM);
|
||||||
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
|
rand->mt[NN-1] = rand->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||||
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];
|
|
||||||
rand->index = 0;
|
rand->index = 0;
|
||||||
}
|
}
|
||||||
y = rand->mt[rand->index++];
|
|
||||||
y ^= (y >> 11);
|
x = rand->mt[rand->index++];
|
||||||
y ^= (y << 7) & TEMPERING_MASK_B;
|
|
||||||
y ^= (y << 15) & TEMPERING_MASK_C;
|
x ^= (x >> 29) & 0x5555555555555555ULL;
|
||||||
y ^= (y >> 18);
|
x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
|
||||||
return y;
|
x ^= (x << 37) & 0xFFF7EEE000000000ULL;
|
||||||
|
x ^= (x >> 43);
|
||||||
|
|
||||||
|
return (int64_t)(x & 0x000FFFFFFFFFFFFFULL); /* return 52-bit value safe for JS */
|
||||||
}
|
}
|
||||||
|
|
||||||
double genRand(MTRand* rand) {
|
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)
|
double rand_range(JSContext *js, double min, double max)
|
||||||
@@ -161,33 +170,6 @@ typedef struct texture_vertex {
|
|||||||
uint8_t r, g, b,a;
|
uint8_t r, g, b,a;
|
||||||
} texture_vertex;
|
} texture_vertex;
|
||||||
|
|
||||||
|
|
||||||
static inline size_t typed_array_bytes(JSTypedArrayEnum type) {
|
|
||||||
switch(type) {
|
|
||||||
case JS_TYPED_ARRAY_UINT8C:
|
|
||||||
case JS_TYPED_ARRAY_INT8:
|
|
||||||
case JS_TYPED_ARRAY_UINT8:
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case JS_TYPED_ARRAY_INT16:
|
|
||||||
case JS_TYPED_ARRAY_UINT16:
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
case JS_TYPED_ARRAY_INT32:
|
|
||||||
case JS_TYPED_ARRAY_UINT32:
|
|
||||||
case JS_TYPED_ARRAY_FLOAT32:
|
|
||||||
return 4;
|
|
||||||
|
|
||||||
case JS_TYPED_ARRAY_BIG_INT64:
|
|
||||||
case JS_TYPED_ARRAY_BIG_UINT64:
|
|
||||||
case JS_TYPED_ARRAY_FLOAT64:
|
|
||||||
return 8;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0; // Return 0 for unknown types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define JS_GETNUM(JS,VAL,I,TO,TYPE) { \
|
#define JS_GETNUM(JS,VAL,I,TO,TYPE) { \
|
||||||
JSValue val = JS_GetPropertyUint32(JS,VAL,I); \
|
JSValue val = JS_GetPropertyUint32(JS,VAL,I); \
|
||||||
TO = js2##TYPE(JS, val); \
|
TO = js2##TYPE(JS, val); \
|
||||||
@@ -261,34 +243,36 @@ void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr)
|
|||||||
JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index)
|
JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index)
|
||||||
{
|
{
|
||||||
JSValue tstack[3];
|
JSValue tstack[3];
|
||||||
tstack[1] = JS_UNDEFINED;
|
tstack[1] = JS_NULL;
|
||||||
tstack[2] = JS_UNDEFINED;
|
tstack[2] = JS_NULL;
|
||||||
// TODO: always copying; implement "takeover"
|
// TODO: always copying; implement "takeover"
|
||||||
tstack[0] = js_new_blob_stoned_copy(js,data,size);//, make_gpu_buffer, NULL, 1);
|
tstack[0] = js_new_blob_stoned_copy(js,data,size);//, make_gpu_buffer, NULL, 1);
|
||||||
JSValue ret = JS_NewTypedArray(js, 3, tstack, type);
|
// JSValue ret = JS_NewTypedArray(js, 3, tstack, type);
|
||||||
JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements));
|
// JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements));
|
||||||
JS_SetPropertyStr(js,ret,"elen", number2js(js,typed_array_bytes(type)));
|
// JS_SetPropertyStr(js,ret,"elen", number2js(js,typed_array_bytes(type)));
|
||||||
JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index));
|
// JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index));
|
||||||
JS_FreeValue(js,tstack[0]);
|
// JS_FreeValue(js,tstack[0]);
|
||||||
return ret;
|
// return ret;
|
||||||
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size)
|
void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size)
|
||||||
{
|
{
|
||||||
size_t o, len, bytes, msize;
|
return NULL;
|
||||||
|
/* size_t o, len, bytes, msize;
|
||||||
JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes);
|
JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes);
|
||||||
void *data = js_get_blob_data(js, &msize, buf);
|
void *data = js_get_blob_data(js, &msize, buf);
|
||||||
JS_FreeValue(js,buf);
|
JS_FreeValue(js,buf);
|
||||||
if (stride) *stride = js_getnum_str(js, argv, "stride");
|
if (stride) *stride = js_getnum_str(js, argv, "stride");
|
||||||
if (size) *size = msize;
|
if (size) *size = msize;
|
||||||
return data;
|
return data;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue make_quad_indices_buffer(JSContext *js, int quads)
|
JSValue make_quad_indices_buffer(JSContext *js, int quads)
|
||||||
{
|
{
|
||||||
cell_rt *rt = JS_GetContextOpaque(js);
|
cell_rt *rt = JS_GetContextOpaque(js);
|
||||||
int count = quads*6;
|
int count = quads*6;
|
||||||
if (!JS_IsUndefined(rt->idx_buffer) && rt->idx_count >= count)
|
if (!JS_IsNull(rt->idx_buffer) && rt->idx_count >= count)
|
||||||
return JS_DupValue(js,rt->idx_buffer);
|
return JS_DupValue(js,rt->idx_buffer);
|
||||||
|
|
||||||
int verts = quads*4;
|
int verts = quads*4;
|
||||||
@@ -302,10 +286,10 @@ JSValue make_quad_indices_buffer(JSContext *js, int quads)
|
|||||||
indices[i+5] = v+1;
|
indices[i+5] = v+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!JS_IsUndefined(rt->idx_buffer))
|
if (!JS_IsNull(rt->idx_buffer))
|
||||||
JS_FreeValue(js,rt->idx_buffer);
|
JS_FreeValue(js,rt->idx_buffer);
|
||||||
|
|
||||||
rt->idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1);
|
// rt->idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1);
|
||||||
rt->idx_count = count;
|
rt->idx_count = count;
|
||||||
return JS_DupValue(js,rt->idx_buffer);
|
return JS_DupValue(js,rt->idx_buffer);
|
||||||
}
|
}
|
||||||
@@ -324,9 +308,9 @@ JSValue quads_to_mesh(JSContext *js, text_vert *buffer)
|
|||||||
color[i] = buffer[i].color;
|
color[i] = buffer[i].color;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
|
JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), 0, 2,0,0);
|
||||||
JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
|
JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), 0, 2,0,0);
|
||||||
JSValue jscolor = make_gpu_buffer(js, color, sizeof(HMM_Vec4)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 4,0,0);
|
JSValue jscolor = make_gpu_buffer(js, color, sizeof(HMM_Vec4)*arrlen(buffer), 0, 4,0,0);
|
||||||
|
|
||||||
size_t quads = verts/4;
|
size_t quads = verts/4;
|
||||||
size_t count = verts/2*3;
|
size_t count = verts/2*3;
|
||||||
@@ -396,10 +380,10 @@ double js2angle(JSContext *js,JSValue v) {
|
|||||||
typedef HMM_Vec4 colorf;
|
typedef HMM_Vec4 colorf;
|
||||||
|
|
||||||
colorf js2color(JSContext *js,JSValue v) {
|
colorf js2color(JSContext *js,JSValue v) {
|
||||||
if (JS_IsUndefined(v)) return (colorf){1,1,1,1};
|
if (JS_IsNull(v)) return (colorf){1,1,1,1};
|
||||||
JSValue c[4];
|
JSValue c[4];
|
||||||
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
|
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
|
||||||
float a = JS_IsUndefined(c[3]) ? 1.0 : js2number(js,c[3]);
|
float a = JS_IsNull(c[3]) ? 1.0 : js2number(js,c[3]);
|
||||||
colorf color = {
|
colorf color = {
|
||||||
.r = js2number(js,c[0]),
|
.r = js2number(js,c[0]),
|
||||||
.g = js2number(js,c[1]),
|
.g = js2number(js,c[1]),
|
||||||
@@ -548,7 +532,7 @@ JSValue vecarr2js(JSContext *js,HMM_Vec2 *points, int n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rect js2rect(JSContext *js,JSValue v) {
|
rect js2rect(JSContext *js,JSValue v) {
|
||||||
if (JS_IsUndefined(v)) return (rect){0,0,1,1};
|
if (JS_IsNull(v)) return (rect){0,0,1,1};
|
||||||
rect rect;
|
rect rect;
|
||||||
JS_GETATOM(js,rect.x,v,x,number)
|
JS_GETATOM(js,rect.x,v,x,number)
|
||||||
JS_GETATOM(js,rect.y,v,y,number)
|
JS_GETATOM(js,rect.y,v,y,number)
|
||||||
@@ -566,7 +550,7 @@ rect js2rect(JSContext *js,JSValue v) {
|
|||||||
|
|
||||||
irect js2irect(JSContext *js, JSValue v)
|
irect js2irect(JSContext *js, JSValue v)
|
||||||
{
|
{
|
||||||
if (JS_IsUndefined(v)) return (irect){0,0,1,1};
|
if (JS_IsNull(v)) return (irect){0,0,1,1};
|
||||||
irect rect;
|
irect rect;
|
||||||
JS_GETATOM(js,rect.x,v,x,number)
|
JS_GETATOM(js,rect.x,v,x,number)
|
||||||
JS_GETATOM(js,rect.y,v,y,number)
|
JS_GETATOM(js,rect.y,v,y,number)
|
||||||
@@ -710,7 +694,7 @@ JSC_CCALL(os_make_text_buffer,
|
|||||||
JSValue js_util_camera_globals(JSContext *js, JSValue self, int argc, JSValue *argv)
|
JSValue js_util_camera_globals(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||||
{
|
{
|
||||||
JSValue camera = argv[0];
|
JSValue camera = argv[0];
|
||||||
if(JS_IsUndefined(camera)) return JS_UNDEFINED;
|
if(JS_IsNull(camera)) return JS_NULL;
|
||||||
|
|
||||||
HMM_Vec2 size; HMM_Vec3 pos; HMM_Quat rotation;
|
HMM_Vec2 size; HMM_Vec3 pos; HMM_Quat rotation;
|
||||||
double fov = 0; int ortho; double near_z = 0; double far_z = 0;
|
double fov = 0; int ortho; double near_z = 0; double far_z = 0;
|
||||||
@@ -831,10 +815,10 @@ JSC_CCALL(array_lerp,
|
|||||||
)
|
)
|
||||||
|
|
||||||
JSValue js_array_get_x(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,0); }
|
JSValue js_array_get_x(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,0); }
|
||||||
JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,0,val); return JS_UNDEFINED; }
|
JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,0,val); return JS_NULL; }
|
||||||
|
|
||||||
JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); }
|
JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); }
|
||||||
JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_UNDEFINED; }
|
JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_NULL; }
|
||||||
|
|
||||||
JSValue js_array_get_xy(JSContext *js, JSValue self)
|
JSValue js_array_get_xy(JSContext *js, JSValue self)
|
||||||
{
|
{
|
||||||
@@ -848,7 +832,7 @@ JSValue js_array_set_xy(JSContext *js, JSValue self, JSValue v)
|
|||||||
{
|
{
|
||||||
JS_SetPropertyUint32(js,self,0,JS_GetPropertyUint32(js,v,0));
|
JS_SetPropertyUint32(js,self,0,JS_GetPropertyUint32(js,v,0));
|
||||||
JS_SetPropertyUint32(js,self,1,JS_GetPropertyUint32(js,v,1));
|
JS_SetPropertyUint32(js,self,1,JS_GetPropertyUint32(js,v,1));
|
||||||
return JS_UNDEFINED;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single-value accessors
|
// Single-value accessors
|
||||||
@@ -857,25 +841,25 @@ JSValue js_array_get_r(JSContext *js, JSValue self)
|
|||||||
{ return JS_GetPropertyUint32(js, self, 0); }
|
{ return JS_GetPropertyUint32(js, self, 0); }
|
||||||
|
|
||||||
JSValue js_array_set_r(JSContext *js, JSValue self, JSValue val)
|
JSValue js_array_set_r(JSContext *js, JSValue self, JSValue val)
|
||||||
{ JS_SetPropertyUint32(js, self, 0, val); return JS_UNDEFINED; }
|
{ JS_SetPropertyUint32(js, self, 0, val); return JS_NULL; }
|
||||||
|
|
||||||
JSValue js_array_get_g(JSContext *js, JSValue self)
|
JSValue js_array_get_g(JSContext *js, JSValue self)
|
||||||
{ return JS_GetPropertyUint32(js, self, 1); }
|
{ return JS_GetPropertyUint32(js, self, 1); }
|
||||||
|
|
||||||
JSValue js_array_set_g(JSContext *js, JSValue self, JSValue val)
|
JSValue js_array_set_g(JSContext *js, JSValue self, JSValue val)
|
||||||
{ JS_SetPropertyUint32(js, self, 1, val); return JS_UNDEFINED; }
|
{ JS_SetPropertyUint32(js, self, 1, val); return JS_NULL; }
|
||||||
|
|
||||||
JSValue js_array_get_b(JSContext *js, JSValue self)
|
JSValue js_array_get_b(JSContext *js, JSValue self)
|
||||||
{ return JS_GetPropertyUint32(js, self, 2); }
|
{ return JS_GetPropertyUint32(js, self, 2); }
|
||||||
|
|
||||||
JSValue js_array_set_b(JSContext *js, JSValue self, JSValue val)
|
JSValue js_array_set_b(JSContext *js, JSValue self, JSValue val)
|
||||||
{ JS_SetPropertyUint32(js, self, 2, val); return JS_UNDEFINED; }
|
{ JS_SetPropertyUint32(js, self, 2, val); return JS_NULL; }
|
||||||
|
|
||||||
JSValue js_array_get_a(JSContext *js, JSValue self)
|
JSValue js_array_get_a(JSContext *js, JSValue self)
|
||||||
{ return JS_GetPropertyUint32(js, self, 3); }
|
{ return JS_GetPropertyUint32(js, self, 3); }
|
||||||
|
|
||||||
JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val)
|
JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val)
|
||||||
{ JS_SetPropertyUint32(js, self, 3, val); return JS_UNDEFINED; }
|
{ JS_SetPropertyUint32(js, self, 3, val); return JS_NULL; }
|
||||||
|
|
||||||
// Multi-value accessors
|
// Multi-value accessors
|
||||||
|
|
||||||
@@ -893,7 +877,7 @@ JSValue js_array_set_rgb(JSContext *js, JSValue self, JSValue val)
|
|||||||
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
|
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
|
||||||
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
|
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
|
||||||
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
|
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
|
||||||
return JS_UNDEFINED;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue js_array_get_rgba(JSContext *js, JSValue self)
|
JSValue js_array_get_rgba(JSContext *js, JSValue self)
|
||||||
@@ -912,7 +896,7 @@ JSValue js_array_set_rgba(JSContext *js, JSValue self, JSValue val)
|
|||||||
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
|
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
|
||||||
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
|
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
|
||||||
JS_SetPropertyUint32(js, self, 3, JS_GetPropertyUint32(js, val, 3));
|
JS_SetPropertyUint32(js, self, 3, JS_GetPropertyUint32(js, val, 3));
|
||||||
return JS_UNDEFINED;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_array_funcs[] = {
|
static const JSCFunctionListEntry js_array_funcs[] = {
|
||||||
@@ -943,15 +927,31 @@ static const JSCFunctionListEntry js_number_funcs[] = {
|
|||||||
PROTO_FUNC_DEF(number, lerp, 2),
|
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,
|
JSC_CCALL(os_guid,
|
||||||
SDL_GUID guid;
|
uint8_t data[16];
|
||||||
randombytes(guid.data, 16);
|
for(int i = 0; i < 4; i++){
|
||||||
|
uint32_t v = xorshift32();
|
||||||
|
memcpy(&data[i*4], &v, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
char guid_str[33];
|
return JS_NewStringLen(js, buf, 32);
|
||||||
|
|
||||||
SDL_GUIDToString(guid, guid_str, 33);
|
|
||||||
|
|
||||||
return JS_NewString(js,guid_str);
|
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_SCALL(console_print, printf("%s", str); )
|
JSC_SCALL(console_print, printf("%s", str); )
|
||||||
@@ -1193,16 +1193,33 @@ JSC_CCALL(os_make_font,
|
|||||||
|
|
||||||
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
|
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)
|
JSValue make_color_buffer(JSContext *js, colorf c, int verts)
|
||||||
{
|
{
|
||||||
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
|
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
|
||||||
for (int i = 0; i < verts; i++)
|
for (int i = 0; i < verts; i++)
|
||||||
colordata[i] = c;
|
colordata[i] = c;
|
||||||
|
|
||||||
return make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, JS_TYPED_ARRAY_FLOAT32, 4, 0, 0);
|
return make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, 0, 4, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSC_CCALL(os_make_line_prim,
|
JSC_CCALL(os_make_line_prim,
|
||||||
|
return JS_NULL;
|
||||||
|
/*
|
||||||
JSValue prim = JS_NewObject(js);
|
JSValue prim = JS_NewObject(js);
|
||||||
HMM_Vec2 *v = js2cpvec2arr(js,argv[0]);
|
HMM_Vec2 *v = js2cpvec2arr(js,argv[0]);
|
||||||
|
|
||||||
@@ -1222,7 +1239,7 @@ JSC_CCALL(os_make_line_prim,
|
|||||||
.closed = JS_ToBool(js,argv[3])
|
.closed = JS_ToBool(js,argv[3])
|
||||||
});
|
});
|
||||||
|
|
||||||
JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, JS_TYPED_ARRAY_FLOAT32, 2,1,0));
|
JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, 0, 2,1,0));
|
||||||
|
|
||||||
JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1));
|
JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1));
|
||||||
|
|
||||||
@@ -1232,7 +1249,7 @@ JSC_CCALL(os_make_line_prim,
|
|||||||
uv[i*2+1] = m->annotations[i].v_across_curve;
|
uv[i*2+1] = m->annotations[i].v_across_curve;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), JS_TYPED_ARRAY_FLOAT32,2,1,0));
|
JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), 0,2,1,0));
|
||||||
JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices));
|
JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices));
|
||||||
JS_SetPropertyStr(js,prim,"color",make_color_buffer(js,js2color(js,argv[4]), m->num_vertices));
|
JS_SetPropertyStr(js,prim,"color",make_color_buffer(js,js2color(js,argv[4]), m->num_vertices));
|
||||||
JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3));
|
JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3));
|
||||||
@@ -1241,10 +1258,11 @@ JSC_CCALL(os_make_line_prim,
|
|||||||
parsl_destroy_context(par_ctx);
|
parsl_destroy_context(par_ctx);
|
||||||
|
|
||||||
return prim;
|
return prim;
|
||||||
|
*/
|
||||||
)
|
)
|
||||||
|
|
||||||
static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
|
static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
|
||||||
if (JS_IsUndefined(ds->callback)) return;
|
if (JS_IsNull(ds->callback)) return;
|
||||||
uint8_t *rgb = malloc(frame->height*frame->width*4);
|
uint8_t *rgb = malloc(frame->height*frame->width*4);
|
||||||
memset(rgb,255,frame->height*frame->width*4);
|
memset(rgb,255,frame->height*frame->width*4);
|
||||||
plm_frame_to_rgba(frame, rgb, frame->width*4);
|
plm_frame_to_rgba(frame, rgb, frame->width*4);
|
||||||
@@ -1260,7 +1278,7 @@ static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
|
|||||||
JSValue s[1];
|
JSValue s[1];
|
||||||
s[0] = surfData;
|
s[0] = surfData;
|
||||||
JSValue cb = JS_DupValue(ds->js,ds->callback);
|
JSValue cb = JS_DupValue(ds->js,ds->callback);
|
||||||
JSValue ret = JS_Call(ds->js, cb, JS_UNDEFINED, 1, s);
|
JSValue ret = JS_Call(ds->js, cb, JS_NULL, 1, s);
|
||||||
JS_FreeValue(ds->js,cb);
|
JS_FreeValue(ds->js,cb);
|
||||||
free(rgb);
|
free(rgb);
|
||||||
uncaught_exception(ds->js,ret);
|
uncaught_exception(ds->js,ret);
|
||||||
@@ -1272,7 +1290,7 @@ JSC_CCALL(os_make_video,
|
|||||||
datastream *ds = ds_openvideo(data, len);
|
datastream *ds = ds_openvideo(data, len);
|
||||||
if (!ds) return JS_ThrowReferenceError(js, "Video file was not valid.");
|
if (!ds) return JS_ThrowReferenceError(js, "Video file was not valid.");
|
||||||
ds->js = js;
|
ds->js = js;
|
||||||
ds->callback = JS_UNDEFINED;
|
ds->callback = JS_NULL;
|
||||||
plm_set_video_decode_callback(ds->plm, render_frame, ds);
|
plm_set_video_decode_callback(ds->plm, render_frame, ds);
|
||||||
return datastream2js(js,ds);
|
return datastream2js(js,ds);
|
||||||
)
|
)
|
||||||
@@ -1296,7 +1314,7 @@ JSC_CCALL(os_rectpack,
|
|||||||
int packed = stbrp_pack_rects(ctx, rects, num);
|
int packed = stbrp_pack_rects(ctx, rects, num);
|
||||||
|
|
||||||
if (!packed) {
|
if (!packed) {
|
||||||
return JS_UNDEFINED;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = JS_NewArray(js);
|
ret = JS_NewArray(js);
|
||||||
@@ -1321,7 +1339,7 @@ JSC_CCALL(os_insertion_sort,
|
|||||||
while (j >= 0) {
|
while (j >= 0) {
|
||||||
JSValue arr_j = JS_GetPropertyUint32(js, arr, j);
|
JSValue arr_j = JS_GetPropertyUint32(js, arr, j);
|
||||||
|
|
||||||
JSValue ret = JS_Call(js, cmp, JS_UNDEFINED, 2, (JSValue[]){ arr_j, key });
|
JSValue ret = JS_Call(js, cmp, JS_NULL, 2, (JSValue[]){ arr_j, key });
|
||||||
if (JS_IsException(ret)) {
|
if (JS_IsException(ret)) {
|
||||||
JS_FreeValue(js,arr_j);
|
JS_FreeValue(js,arr_j);
|
||||||
JS_FreeValue(js,key);
|
JS_FreeValue(js,key);
|
||||||
@@ -1349,13 +1367,13 @@ JSC_CCALL(os_power_state,
|
|||||||
SDL_PowerState state = SDL_GetPowerInfo(NULL, NULL);
|
SDL_PowerState state = SDL_GetPowerInfo(NULL, NULL);
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case SDL_POWERSTATE_ERROR: return JS_ThrowTypeError(js, "Error determining power status");
|
case SDL_POWERSTATE_ERROR: return JS_ThrowTypeError(js, "Error determining power status");
|
||||||
case SDL_POWERSTATE_UNKNOWN: return JS_UNDEFINED;
|
case SDL_POWERSTATE_UNKNOWN: return JS_NULL;
|
||||||
case SDL_POWERSTATE_ON_BATTERY: return JS_NewString(js, "on battery");
|
case SDL_POWERSTATE_ON_BATTERY: return JS_NewString(js, "on battery");
|
||||||
case SDL_POWERSTATE_NO_BATTERY: return JS_NewString(js, "no battery");
|
case SDL_POWERSTATE_NO_BATTERY: return JS_NewString(js, "no battery");
|
||||||
case SDL_POWERSTATE_CHARGING: return JS_NewString(js, "charging");
|
case SDL_POWERSTATE_CHARGING: return JS_NewString(js, "charging");
|
||||||
case SDL_POWERSTATE_CHARGED: return JS_NewString(js, "charged");
|
case SDL_POWERSTATE_CHARGED: return JS_NewString(js, "charged");
|
||||||
}
|
}
|
||||||
return JS_UNDEFINED;
|
return JS_NULL;
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(os_cull_sprites,
|
JSC_CCALL(os_cull_sprites,
|
||||||
@@ -1490,8 +1508,8 @@ js_##NAME = JS_NewObject(js); \
|
|||||||
JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs)); \
|
JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs)); \
|
||||||
JS_SetPrototype(js, js_##NAME, PARENT); \
|
JS_SetPrototype(js, js_##NAME, PARENT); \
|
||||||
|
|
||||||
JSValue js_layout_use(JSContext *js);
|
|
||||||
JSValue js_miniz_use(JSContext *js);
|
JSValue js_miniz_use(JSContext *js);
|
||||||
|
JSValue js_num_use(JSContext *js);
|
||||||
|
|
||||||
JSValue js_graphics_use(JSContext *js) {
|
JSValue js_graphics_use(JSContext *js) {
|
||||||
JSValue mod = JS_NewObject(js);
|
JSValue mod = JS_NewObject(js);
|
||||||
@@ -1526,6 +1544,9 @@ JSC_CCALL(os_value_id,
|
|||||||
#include "qjs_time.h"
|
#include "qjs_time.h"
|
||||||
#include "qjs_http.h"
|
#include "qjs_http.h"
|
||||||
#include "qjs_wota.h"
|
#include "qjs_wota.h"
|
||||||
|
#include "qjs_socket.h"
|
||||||
|
#include "qjs_nota.h"
|
||||||
|
#include "qjs_ffi.h"
|
||||||
|
|
||||||
//JSValue js_imgui_use(JSContext *js);
|
//JSValue js_imgui_use(JSContext *js);
|
||||||
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
|
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
|
||||||
@@ -1536,7 +1557,9 @@ void ffi_load(JSContext *js)
|
|||||||
|
|
||||||
JS_FreeValue(js, js_blob_use(js)); // juice blob
|
JS_FreeValue(js, js_blob_use(js)); // juice blob
|
||||||
|
|
||||||
m_seedRand(&rt->mrand, time(NULL));
|
uint64_t rr;
|
||||||
|
randombytes(&rr,4);
|
||||||
|
m_seedRand(&rt->mrand, rr);
|
||||||
|
|
||||||
// cell modules
|
// cell modules
|
||||||
arrput(rt->module_registry, MISTLINE(time));
|
arrput(rt->module_registry, MISTLINE(time));
|
||||||
@@ -1546,11 +1569,20 @@ void ffi_load(JSContext *js)
|
|||||||
// extra
|
// extra
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
|
||||||
|
arrput(rt->module_registry, MISTLINE(socket));
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
|
||||||
arrput(rt->module_registry, MISTLINE(qr));
|
arrput(rt->module_registry, MISTLINE(qr));
|
||||||
arrput(rt->module_registry, MISTLINE(http));
|
arrput(rt->module_registry, MISTLINE(http));
|
||||||
arrput(rt->module_registry, MISTLINE(crypto));
|
arrput(rt->module_registry, MISTLINE(crypto));
|
||||||
arrput(rt->module_registry, MISTLINE(miniz));
|
arrput(rt->module_registry, MISTLINE(miniz));
|
||||||
|
arrput(rt->module_registry, MISTLINE(num));
|
||||||
|
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));
|
||||||
|
arrput(rt->module_registry, MISTLINE(ffi));
|
||||||
|
|
||||||
// power user
|
// power user
|
||||||
arrput(rt->module_registry, MISTLINE(js));
|
arrput(rt->module_registry, MISTLINE(js));
|
||||||
@@ -1568,7 +1600,6 @@ void ffi_load(JSContext *js)
|
|||||||
arrput(rt->module_registry, MISTLINE(graphics));
|
arrput(rt->module_registry, MISTLINE(graphics));
|
||||||
arrput(rt->module_registry, MISTLINE(video));
|
arrput(rt->module_registry, MISTLINE(video));
|
||||||
arrput(rt->module_registry, MISTLINE(soloud));
|
arrput(rt->module_registry, MISTLINE(soloud));
|
||||||
arrput(rt->module_registry, MISTLINE(layout));
|
|
||||||
|
|
||||||
// arrput(rt->module_registry, MISTLINE(imgui));
|
// arrput(rt->module_registry, MISTLINE(imgui));
|
||||||
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
|
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
|
||||||
@@ -1585,15 +1616,6 @@ void ffi_load(JSContext *js)
|
|||||||
|
|
||||||
JSValue prosp = JS_NewObject(js);
|
JSValue prosp = JS_NewObject(js);
|
||||||
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
|
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
|
||||||
JSValue c_types = JS_NewObject(js);
|
|
||||||
JS_SetPropertyStr(js,prosp, "c_types", c_types);
|
|
||||||
|
|
||||||
QJSCLASSPREP_FUNCS(font);
|
|
||||||
QJSCLASSPREP_FUNCS(datastream);
|
|
||||||
|
|
||||||
JSValue jsobject = JS_GetPropertyStr(js,globalThis, "Object");
|
|
||||||
JS_SetPropertyStr(js, jsobject, "id", JS_NewCFunction(js, js_os_value_id, "id", 1));
|
|
||||||
JS_FreeValue(js,jsobject);
|
|
||||||
|
|
||||||
JSValue jsarray = JS_GetPropertyStr(js,globalThis, "Array");
|
JSValue jsarray = JS_GetPropertyStr(js,globalThis, "Array");
|
||||||
JSValue array_proto = JS_GetPropertyStr(js,jsarray, "prototype");
|
JSValue array_proto = JS_GetPropertyStr(js,jsarray, "prototype");
|
||||||
@@ -1607,9 +1629,6 @@ void ffi_load(JSContext *js)
|
|||||||
JS_FreeValue(js,jsnumber);
|
JS_FreeValue(js,jsnumber);
|
||||||
JS_FreeValue(js,number_proto);
|
JS_FreeValue(js,number_proto);
|
||||||
|
|
||||||
//JS_SetPropertyStr(js,prosp, "version", JS_NewString(js,CELL_VERSION));
|
|
||||||
//JS_SetPropertyStr(js,prosp,"revision",JS_NewString(js,CELL_COMMIT));
|
|
||||||
|
|
||||||
JSValue hidden_fn = JS_NewObject(js);
|
JSValue hidden_fn = JS_NewObject(js);
|
||||||
// add engine.js-only functions to hidden_fn. It should grab them and then remove so nothing else can use them.
|
// add engine.js-only functions to hidden_fn. It should grab them and then remove so nothing else can use them.
|
||||||
|
|
||||||
@@ -1623,6 +1642,17 @@ void ffi_load(JSContext *js)
|
|||||||
// Add functions that should only be accessible to engine.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_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, "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;";
|
||||||
|
|
||||||
|
JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", 0);
|
||||||
|
|
||||||
|
JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym);
|
||||||
|
|
||||||
|
rt->actor_sym = JS_ValueToAtom(js, actorsym);
|
||||||
|
|
||||||
if (rt->init_wota) {
|
if (rt->init_wota) {
|
||||||
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, rt->init_wota));
|
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, rt->init_wota));
|
||||||
@@ -1631,16 +1661,6 @@ void ffi_load(JSContext *js)
|
|||||||
rt->init_wota = NULL;
|
rt->init_wota = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cell_rt *actor = JS_GetContextOpaque(js);
|
|
||||||
JSValue actorsym = js_newsymbol(js, "actor symbol", 0);
|
|
||||||
actor->actor_sym = JS_ValueToAtom(js, actorsym);
|
|
||||||
JS_SetPropertyStr(js, hidden_fn, "ACTORDATA", JS_DupValue(js,actorsym));
|
|
||||||
JS_FreeValue(js, actorsym);
|
|
||||||
JSValue docsym = js_newsymbol(js, "+documentation+", 0);
|
|
||||||
actor->doc_sym = JS_ValueToAtom(js, docsym);
|
|
||||||
JS_SetPropertyStr(js, hidden_fn, "DOCSYM", JS_DupValue(js,docsym));
|
|
||||||
JS_FreeValue(js,docsym);
|
|
||||||
|
|
||||||
JS_SetPropertyStr(js, prosp, "hidden", hidden_fn);
|
JS_SetPropertyStr(js, prosp, "hidden", hidden_fn);
|
||||||
|
|
||||||
JS_FreeValue(js,globalThis);
|
JS_FreeValue(js,globalThis);
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ typedef struct tagMTRand MTRand;
|
|||||||
|
|
||||||
// Random number generation functions
|
// Random number generation functions
|
||||||
double genRand(MTRand *mrand);
|
double genRand(MTRand *mrand);
|
||||||
uint32_t genRandLong(MTRand *mrand);
|
int64_t genRandLong(MTRand *mrand);
|
||||||
void m_seedRand(MTRand *mrand, uint32_t seed);
|
void m_seedRand(MTRand *mrand, uint64_t seed);
|
||||||
double rand_range(JSContext *js, double min, double max);
|
double rand_range(JSContext *js, double min, double max);
|
||||||
|
|
||||||
// Common data structures
|
// Common data structures
|
||||||
|
|||||||
12
source/kim.h
12
source/kim.h
@@ -10,17 +10,17 @@ void kim_to_utf8(char **kim, char **utf, int runes);
|
|||||||
// Return the number of runes in a utf8 string
|
// Return the number of runes in a utf8 string
|
||||||
int utf8_count(const char *utf8);
|
int utf8_count(const char *utf8);
|
||||||
|
|
||||||
|
int decode_utf8(char **s);
|
||||||
|
void encode_utf8(char **s, int code);
|
||||||
|
void encode_kim(char **s, int code);
|
||||||
|
int decode_kim(char **s);
|
||||||
|
|
||||||
#ifdef KIM_IMPLEMENTATION
|
#ifdef KIM_IMPLEMENTATION
|
||||||
|
|
||||||
#define KIM_CONT 0x80
|
#define KIM_CONT 0x80
|
||||||
#define KIM_DATA 0x7f
|
#define KIM_DATA 0x7f
|
||||||
#define CONTINUE(CHAR) (CHAR>>7)
|
#define CONTINUE(CHAR) (CHAR>>7)
|
||||||
|
|
||||||
int decode_utf8(char **s);
|
|
||||||
void encode_utf8(char **s, int code);
|
|
||||||
static void encode_kim(char **s, int code);
|
|
||||||
int decode_kim(char **s);
|
|
||||||
|
|
||||||
static inline int utf8_bytes(char c)
|
static inline int utf8_bytes(char c)
|
||||||
{
|
{
|
||||||
int bytes = __builtin_clz(~(c));
|
int bytes = __builtin_clz(~(c));
|
||||||
@@ -70,7 +70,7 @@ void encode_utf8(char **s, int rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write and advance s with rune in kim
|
// write and advance s with rune in kim
|
||||||
static inline void encode_kim(char **s, int rune)
|
void encode_kim(char **s, int rune)
|
||||||
{
|
{
|
||||||
if (rune < KIM_CONT) {
|
if (rune < KIM_CONT) {
|
||||||
**s = 0 | (KIM_DATA & rune);
|
**s = 0 | (KIM_DATA & rune);
|
||||||
|
|||||||
67
source/libregexp-opcode.h
Normal file
67
source/libregexp-opcode.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Regular Expression Engine
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017-2018 Fabrice Bellard
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef DEF
|
||||||
|
|
||||||
|
DEF(invalid, 1) /* never used */
|
||||||
|
DEF(char, 3)
|
||||||
|
DEF(char_i, 3)
|
||||||
|
DEF(char32, 5)
|
||||||
|
DEF(char32_i, 5)
|
||||||
|
DEF(dot, 1)
|
||||||
|
DEF(any, 1) /* same as dot but match any character including line terminator */
|
||||||
|
DEF(line_start, 1)
|
||||||
|
DEF(line_start_m, 1)
|
||||||
|
DEF(line_end, 1)
|
||||||
|
DEF(line_end_m, 1)
|
||||||
|
DEF(goto, 5)
|
||||||
|
DEF(split_goto_first, 5)
|
||||||
|
DEF(split_next_first, 5)
|
||||||
|
DEF(match, 1)
|
||||||
|
DEF(save_start, 2) /* save start position */
|
||||||
|
DEF(save_end, 2) /* save end position, must come after saved_start */
|
||||||
|
DEF(save_reset, 3) /* reset save positions */
|
||||||
|
DEF(loop, 5) /* decrement the top the stack and goto if != 0 */
|
||||||
|
DEF(push_i32, 5) /* push integer on the stack */
|
||||||
|
DEF(drop, 1)
|
||||||
|
DEF(word_boundary, 1)
|
||||||
|
DEF(word_boundary_i, 1)
|
||||||
|
DEF(not_word_boundary, 1)
|
||||||
|
DEF(not_word_boundary_i, 1)
|
||||||
|
DEF(back_reference, 2)
|
||||||
|
DEF(back_reference_i, 2) /* must come after */
|
||||||
|
DEF(backward_back_reference, 2) /* must come after */
|
||||||
|
DEF(backward_back_reference_i, 2) /* must come after */
|
||||||
|
DEF(range, 3) /* variable length */
|
||||||
|
DEF(range_i, 3) /* variable length */
|
||||||
|
DEF(range32, 3) /* variable length */
|
||||||
|
DEF(range32_i, 3) /* variable length */
|
||||||
|
DEF(lookahead, 5)
|
||||||
|
DEF(negative_lookahead, 5)
|
||||||
|
DEF(push_char_pos, 1) /* push the character position on the stack */
|
||||||
|
DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */
|
||||||
|
DEF(prev, 1) /* go to the previous char */
|
||||||
|
DEF(simple_greedy_quant, 17)
|
||||||
|
|
||||||
|
#endif /* DEF */
|
||||||
3259
source/libregexp.c
Normal file
3259
source/libregexp.c
Normal file
File diff suppressed because it is too large
Load Diff
61
source/libregexp.h
Normal file
61
source/libregexp.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Regular Expression Engine
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017-2018 Fabrice Bellard
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef LIBREGEXP_H
|
||||||
|
#define LIBREGEXP_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define LRE_FLAG_GLOBAL (1 << 0)
|
||||||
|
#define LRE_FLAG_IGNORECASE (1 << 1)
|
||||||
|
#define LRE_FLAG_MULTILINE (1 << 2)
|
||||||
|
#define LRE_FLAG_DOTALL (1 << 3)
|
||||||
|
#define LRE_FLAG_UNICODE (1 << 4)
|
||||||
|
#define LRE_FLAG_STICKY (1 << 5)
|
||||||
|
#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */
|
||||||
|
#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */
|
||||||
|
#define LRE_FLAG_UNICODE_SETS (1 << 8)
|
||||||
|
|
||||||
|
#define LRE_RET_MEMORY_ERROR (-1)
|
||||||
|
#define LRE_RET_TIMEOUT (-2)
|
||||||
|
|
||||||
|
uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size,
|
||||||
|
const char *buf, size_t buf_len, int re_flags,
|
||||||
|
void *opaque);
|
||||||
|
int lre_get_capture_count(const uint8_t *bc_buf);
|
||||||
|
int lre_get_flags(const uint8_t *bc_buf);
|
||||||
|
const char *lre_get_groupnames(const uint8_t *bc_buf);
|
||||||
|
int lre_exec(uint8_t **capture,
|
||||||
|
const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen,
|
||||||
|
int cbuf_type, void *opaque);
|
||||||
|
|
||||||
|
int lre_parse_escape(const uint8_t **pp, int allow_utf16);
|
||||||
|
|
||||||
|
/* must be provided by the user, return non zero if overflow */
|
||||||
|
int lre_check_stack_overflow(void *opaque, size_t alloca_size);
|
||||||
|
/* must be provided by the user, return non zero if time out */
|
||||||
|
int lre_check_timeout(void *opaque);
|
||||||
|
void *lre_realloc(void *opaque, void *ptr, size_t size);
|
||||||
|
|
||||||
|
#endif /* LIBREGEXP_H */
|
||||||
5149
source/libunicode-table.h
Normal file
5149
source/libunicode-table.h
Normal file
File diff suppressed because it is too large
Load Diff
2123
source/libunicode.c
Normal file
2123
source/libunicode.c
Normal file
File diff suppressed because it is too large
Load Diff
186
source/libunicode.h
Normal file
186
source/libunicode.h
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Unicode utilities
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017-2018 Fabrice Bellard
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef LIBUNICODE_H
|
||||||
|
#define LIBUNICODE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* define it to include all the unicode tables (40KB larger) */
|
||||||
|
#define CONFIG_ALL_UNICODE
|
||||||
|
|
||||||
|
#define LRE_CC_RES_LEN_MAX 3
|
||||||
|
|
||||||
|
/* char ranges */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int len; /* in points, always even */
|
||||||
|
int size;
|
||||||
|
uint32_t *points; /* points sorted by increasing value */
|
||||||
|
void *mem_opaque;
|
||||||
|
void *(*realloc_func)(void *opaque, void *ptr, size_t size);
|
||||||
|
} CharRange;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CR_OP_UNION,
|
||||||
|
CR_OP_INTER,
|
||||||
|
CR_OP_XOR,
|
||||||
|
CR_OP_SUB,
|
||||||
|
} CharRangeOpEnum;
|
||||||
|
|
||||||
|
void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size));
|
||||||
|
void cr_free(CharRange *cr);
|
||||||
|
int cr_realloc(CharRange *cr, int size);
|
||||||
|
int cr_copy(CharRange *cr, const CharRange *cr1);
|
||||||
|
|
||||||
|
static inline int cr_add_point(CharRange *cr, uint32_t v)
|
||||||
|
{
|
||||||
|
if (cr->len >= cr->size) {
|
||||||
|
if (cr_realloc(cr, cr->len + 1))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cr->points[cr->len++] = v;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2)
|
||||||
|
{
|
||||||
|
if ((cr->len + 2) > cr->size) {
|
||||||
|
if (cr_realloc(cr, cr->len + 2))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cr->points[cr->len++] = c1;
|
||||||
|
cr->points[cr->len++] = c2;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len,
|
||||||
|
const uint32_t *b_pt, int b_len, int op);
|
||||||
|
int cr_op1(CharRange *cr, const uint32_t *b_pt, int b_len, int op);
|
||||||
|
|
||||||
|
static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2)
|
||||||
|
{
|
||||||
|
uint32_t b_pt[2];
|
||||||
|
b_pt[0] = c1;
|
||||||
|
b_pt[1] = c2 + 1;
|
||||||
|
return cr_op1(cr, b_pt, 2, CR_OP_UNION);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cr_invert(CharRange *cr);
|
||||||
|
|
||||||
|
int cr_regexp_canonicalize(CharRange *cr, int is_unicode);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UNICODE_NFC,
|
||||||
|
UNICODE_NFD,
|
||||||
|
UNICODE_NFKC,
|
||||||
|
UNICODE_NFKD,
|
||||||
|
} UnicodeNormalizationEnum;
|
||||||
|
|
||||||
|
int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len,
|
||||||
|
UnicodeNormalizationEnum n_type,
|
||||||
|
void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size));
|
||||||
|
|
||||||
|
/* Unicode character range functions */
|
||||||
|
|
||||||
|
int unicode_script(CharRange *cr, const char *script_name, int is_ext);
|
||||||
|
int unicode_general_category(CharRange *cr, const char *gc_name);
|
||||||
|
int unicode_prop(CharRange *cr, const char *prop_name);
|
||||||
|
|
||||||
|
typedef void UnicodeSequencePropCB(void *opaque, const uint32_t *buf, int len);
|
||||||
|
int unicode_sequence_prop(const char *prop_name, UnicodeSequencePropCB *cb, void *opaque,
|
||||||
|
CharRange *cr);
|
||||||
|
|
||||||
|
int lre_case_conv(uint32_t *res, uint32_t c, int conv_type);
|
||||||
|
int lre_canonicalize(uint32_t c, int is_unicode);
|
||||||
|
|
||||||
|
/* Code point type categories */
|
||||||
|
enum {
|
||||||
|
UNICODE_C_SPACE = (1 << 0),
|
||||||
|
UNICODE_C_DIGIT = (1 << 1),
|
||||||
|
UNICODE_C_UPPER = (1 << 2),
|
||||||
|
UNICODE_C_LOWER = (1 << 3),
|
||||||
|
UNICODE_C_UNDER = (1 << 4),
|
||||||
|
UNICODE_C_DOLLAR = (1 << 5),
|
||||||
|
UNICODE_C_XDIGIT = (1 << 6),
|
||||||
|
};
|
||||||
|
extern uint8_t const lre_ctype_bits[256];
|
||||||
|
|
||||||
|
/* zero or non-zero return value */
|
||||||
|
int lre_is_cased(uint32_t c);
|
||||||
|
int lre_is_case_ignorable(uint32_t c);
|
||||||
|
int lre_is_id_start(uint32_t c);
|
||||||
|
int lre_is_id_continue(uint32_t c);
|
||||||
|
|
||||||
|
static inline int lre_is_space_byte(uint8_t c) {
|
||||||
|
return lre_ctype_bits[c] & UNICODE_C_SPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int lre_is_id_start_byte(uint8_t c) {
|
||||||
|
return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER |
|
||||||
|
UNICODE_C_UNDER | UNICODE_C_DOLLAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int lre_is_id_continue_byte(uint8_t c) {
|
||||||
|
return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER |
|
||||||
|
UNICODE_C_UNDER | UNICODE_C_DOLLAR |
|
||||||
|
UNICODE_C_DIGIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lre_is_space_non_ascii(uint32_t c);
|
||||||
|
|
||||||
|
static inline int lre_is_space(uint32_t c) {
|
||||||
|
if (c < 256)
|
||||||
|
return lre_is_space_byte(c);
|
||||||
|
else
|
||||||
|
return lre_is_space_non_ascii(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int lre_js_is_ident_first(uint32_t c) {
|
||||||
|
if (c < 128) {
|
||||||
|
return lre_is_id_start_byte(c);
|
||||||
|
} else {
|
||||||
|
#ifdef CONFIG_ALL_UNICODE
|
||||||
|
return lre_is_id_start(c);
|
||||||
|
#else
|
||||||
|
return !lre_is_space_non_ascii(c);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int lre_js_is_ident_next(uint32_t c) {
|
||||||
|
if (c < 128) {
|
||||||
|
return lre_is_id_continue_byte(c);
|
||||||
|
} else {
|
||||||
|
/* ZWNJ and ZWJ are accepted in identifiers */
|
||||||
|
if (c >= 0x200C && c <= 0x200D)
|
||||||
|
return TRUE;
|
||||||
|
#ifdef CONFIG_ALL_UNICODE
|
||||||
|
return lre_is_id_continue(c);
|
||||||
|
#else
|
||||||
|
return !lre_is_space_non_ascii(c);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LIBUNICODE_H */
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user