70 Commits
box2d ... steam

Author SHA1 Message Date
John Alanbrook
019f88c2bc initial try 2025-06-04 14:41:47 -05:00
John Alanbrook
cdf8686c64 fix video start
Some checks failed
Build and Deploy / build-macos (push) Failing after 10s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-06-03 08:42:55 -05:00
John Alanbrook
2fdf74f6ee fix blob; throw on non stone reads; update blob test 2025-06-02 13:23:05 -05:00
John Alanbrook
e689679aac add checking for new mod versions 2025-06-02 12:12:05 -05:00
John Alanbrook
f70f65d1c3 add man files; add mod hash checking; add text decoding for blob 2025-06-02 11:10:18 -05:00
John Alanbrook
d9b316270d add text function 2025-06-02 10:35:30 -05:00
John Alanbrook
e2668b330e omit port when sending request if 80 or 443 2025-06-02 09:25:38 -05:00
John Alanbrook
90b5d1430f vendor qjs_miniz 2025-06-02 08:49:02 -05:00
John Alanbrook
7c47c43655 improve globfs performance 2025-06-02 08:48:49 -05:00
John Alanbrook
9e45219706 add clean command 2025-06-02 08:18:26 -05:00
John Alanbrook
d098800c88 fix path mounting 2025-06-02 08:18:08 -05:00
John Alanbrook
3a40076958 fix http; faster blob; list and help commands 2025-06-01 09:34:15 -05:00
John Alanbrook
06108df3d4 fix blob and http 2025-05-31 17:57:17 -05:00
John Alanbrook
a442cf5a4d update to new naming scheme 2025-05-31 16:45:56 -05:00
John Alanbrook
6dee29d213 Merge branch 'master' into modules 2025-05-31 15:58:06 -05:00
John Alanbrook
7711c644a0 congif from .cell/cell.toml
Some checks failed
Build and Deploy / build-macos (push) Failing after 10s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-31 15:55:24 -05:00
John Alanbrook
aab0a56349 fix routing 2025-05-31 15:32:30 -05:00
John Alanbrook
13245bbc98 register root actor 2025-05-31 09:02:53 -05:00
John Alanbrook
c25166d35a no more js leaking on free 2025-05-31 02:39:36 -05:00
John Alanbrook
fc09693c93 test program 2025-05-30 19:11:33 -05:00
John Alanbrook
b71c72db8b remove actors being created via cmd line args 2025-05-30 18:05:02 -05:00
John Alanbrook
66591e32b5 fixes #13: actor files can now be named use
Some checks failed
Build and Deploy / build-macos (push) Failing after 8s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-30 15:34:46 -05:00
John Alanbrook
fba05fa0fb update gitignore 2025-05-30 15:29:22 -05:00
John Alanbrook
11357d4fb5 actor files now .ce; module files now .cm; add toml encoder/decoder and test 2025-05-30 15:25:31 -05:00
John Alanbrook
674eb237e0 start of rename to cell 2025-05-30 12:07:03 -05:00
John Alanbrook
939269b060 initial modules attempt
Some checks failed
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-linux (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
2025-05-29 18:48:19 -05:00
John Alanbrook
f54200a7dd cwd works correctly for when running from a different folder
Some checks failed
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-29 17:59:33 -05:00
John Alanbrook
9ae2357493 new tracy build options 2025-05-29 14:11:47 -05:00
John Alanbrook
da525cd111 add compiling and saving bytecode 2025-05-29 13:55:03 -05:00
John Alanbrook
c3f07c0ef5 separate out cell stuff & prosperon stuff 2025-05-29 13:54:42 -05:00
John Alanbrook
2e7643aa2a add stone function 2025-05-29 11:28:51 -05:00
John Alanbrook
aca9baf585 add docstring symbol to C level actor 2025-05-29 10:29:57 -05:00
John Alanbrook
b4371ba3e0 improve time 2025-05-29 02:56:30 -05:00
John Alanbrook
4e118dd8e9 fix parseq and parseq test 2025-05-29 02:19:24 -05:00
John Alanbrook
9279e21b84 moth now filters events to the correct space 2025-05-29 01:21:45 -05:00
John Alanbrook
8d9bb4a2c9 use log instead of console 2025-05-29 00:39:14 -05:00
John Alanbrook
1040c61863 fix blob usage errors
Some checks failed
Build and Deploy / build-macos (push) Failing after 8s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-28 23:56:18 -05:00
John Alanbrook
e86bdf52fe switch to blobs from arraybuffers
Some checks failed
Build and Deploy / build-linux (push) Failing after 1m29s
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
2025-05-28 22:33:32 -05:00
John Alanbrook
53b3f0af9c fix mutex race
Some checks failed
Build and Deploy / build-linux (push) Failing after 1m27s
Build and Deploy / build-macos (push) Failing after 8s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
2025-05-28 18:50:39 -05:00
John Alanbrook
09f48d08b9 update chess to use moth
Some checks failed
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-28 17:49:37 -05:00
John Alanbrook
4eb592b740 moth handles camera now 2025-05-28 16:47:27 -05:00
John Alanbrook
c603e8f006 separate out blob and quickjs hooks into a blob.h header 2025-05-28 14:55:35 -05:00
John Alanbrook
f334a2ad56 expand qjs_blob 2025-05-28 14:38:43 -05:00
John Alanbrook
a39f287a88 remove prosperon.on and prosperon.dispatch 2025-05-28 13:51:58 -05:00
John Alanbrook
758b3e4704 remove tracy if not specified on cmd line 2025-05-28 13:16:08 -05:00
John Alanbrook
aa70dcbdc2 update 2025-05-28 02:28:20 -05:00
John Alanbrook
3667d53eae tracy is cell level now 2025-05-27 17:06:03 -05:00
John Alanbrook
01df337ccc draw2d now generates high level commands; turned into instructions by moth 2025-05-27 13:46:56 -05:00
John Alanbrook
ad182d68ec announce useful functions in qjs_common.h and remove externs 2025-05-27 10:23:07 -05:00
John Alanbrook
f7dcc8f57c correct pixelformat bug 2025-05-27 10:03:40 -05:00
John Alanbrook
f73f738459 add fd module 2025-05-27 01:52:27 -05:00
John Alanbrook
bf74a3c7d4 move keyboard and mouse functions that are main thread only to sdl_video
Some checks failed
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
2025-05-27 01:30:50 -05:00
John Alanbrook
e8fb50659d add colorspace support; fix webcam 2025-05-27 01:00:55 -05:00
John Alanbrook
00df0899fa add nv12 pixel 2025-05-26 23:37:10 -05:00
John Alanbrook
ae5ba67fc8 surface pixel handling
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-26 22:43:50 -05:00
John Alanbrook
bc929988b2 extend camera
Some checks failed
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-26 20:55:57 -05:00
John Alanbrook
2346040d46 matching actor2js function; sdl_video returns actor now 2025-05-26 18:00:42 -05:00
John Alanbrook
2eb6b3e0b4 input now contains function to register any actor to OS events 2025-05-26 17:56:43 -05:00
John Alanbrook
2edcd89780 add sdl cursor support 2025-05-26 16:24:19 -05:00
John Alanbrook
a63e5c5b55 move surface to its own module 2025-05-26 15:59:28 -05:00
John Alanbrook
af21e10e97 draw textures with draw2d
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-26 13:25:56 -05:00
John Alanbrook
1b97527120 draw2d uses object prototype for command creation 2025-05-26 12:53:41 -05:00
John Alanbrook
8074e2a82e draw2d now can send batches of draws to video backends 2025-05-26 12:48:19 -05:00
John Alanbrook
db1afb6477 drop message sends if message has no return instead of throw error
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-05-26 09:27:58 -05:00
John Alanbrook
45311408d6 render command ops 2025-05-26 00:57:29 -05:00
John Alanbrook
1141fca63a add window message handling for sdl actor 2025-05-25 22:47:35 -05:00
John Alanbrook
7b70def11f more full window object
Some checks failed
Build and Deploy / build-windows (CLANG64) (push) Waiting to run
Build and Deploy / package-dist (push) Blocked by required conditions
Build and Deploy / deploy-itch (push) Blocked by required conditions
Build and Deploy / deploy-gitea (push) Blocked by required conditions
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-linux (push) Failing after 1m27s
2025-05-25 21:55:21 -05:00
John Alanbrook
aac0c3813b window actor now created when use('sdl_video') is called 2025-05-25 19:06:24 -05:00
John Alanbrook
49786842f0 pull out sdl_video into its own module 2025-05-25 18:02:43 -05:00
John Alanbrook
9f9dfe03a6 removed internal functions from accessible via use
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-linux (push) Failing after 2m15s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
2025-05-25 11:51:01 -05:00
539 changed files with 17284 additions and 10232 deletions

8
.cell/cell.toml Normal file
View File

@@ -0,0 +1,8 @@
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master"
[system]
ar_timer = 60 # seconds before idle actor reclamation
actor_memory = 0 # MB of memory an actor can use; 0 for unbounded
net_service = 0.1 # seconds per net service pull
reply_timeout = 60 # seconds to hold callback for reply messages; 0 for unbounded

6
.cell/lock.toml Normal file
View File

@@ -0,0 +1,6 @@
[modules]
[modules.extramath]
hash = "MCLZT3JABTAENS4WVXKGWJ7JPBLZER4YQ5VN2PE7ZD2Z4WYGTIMA===="
url = "https://gitea.pockle.world/john/extramath@master"
downloaded = "Monday June 2 12:07:20.42 PM -5 2025 AD"
commit = "84d81a19a8455bcf8dc494739e9e6d545df6ff2c"

13
.gitignore vendored
View File

@@ -6,27 +6,18 @@ build/
*.o *.o
*.a *.a
*.d *.d
tags
Jenkinsfile
*~ *~
*.log *.log
*.gz *.gz
*.tar *.tar
.nova/ .nova/
packer*
primum
sokol-shdc*
source/shaders/*.h source/shaders/*.h
core.cdb
primum.exe
core.cdb.h
jsc
.DS_Store .DS_Store
*.html *.html
.vscode .vscode
*.icns *.icns
game.zip
icon.ico icon.ico
steam/ steam/
subprojects/*/ subprojects/*/
build_dbg/ build_dbg/
modules/

View File

@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Build Commands ## Build Commands
### Build variants ### Build variants
- `make debug` - Build debug version (uses meson debug configuration) - `make` - Make and install debug version. Usually all that's needed.
- `make fast` - Build optimized version - `make fast` - Build optimized version
- `make release` - Build release version with LTO and optimizations - `make release` - Build release version with LTO and optimizations
- `make small` - Build minimal size version - `make small` - Build minimal size version
@@ -13,10 +13,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `make crosswin` - Cross-compile for Windows using mingw32 - `make crosswin` - Cross-compile for Windows using mingw32
### Testing ### Testing
- `meson test -C build_dbg` - Run all tests in debug build After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
- `meson test -C build_<variant>` - Run tests in specific build variant
- `./build_dbg/prosperon tests/<testname>.js` - Run specific test ## Scripting language
- Available tests: `spawn_actor`, `empty`, `nota`, `wota`, `portalspawner`, `overling`, `send`, `delay` This is called "cell", but it is is a variant of javascript and extremely similar.
### Common development commands ### Common development commands
- `meson setup build_<variant>` - Configure build directory - `meson setup build_<variant>` - Configure build directory
@@ -126,7 +126,7 @@ meson test -C build_dbg
### Debugging ### Debugging
- Use debug build: `make debug` - Use debug build: `make debug`
- Tracy profiler support when enabled - Tracy profiler support when enabled
- Console logging available via `console.log()`, `console.error()`, etc. - Console logging available via `log.console()`, `log.error()`, etc.
- Log files written to `.prosperon/log.txt` - Log files written to `.prosperon/log.txt`
# Project Structure Notes # Project Structure Notes
@@ -202,6 +202,11 @@ meson test -C build_dbg
### Utility Modules ### Utility Modules
- `time` - Time management and delays - `time` - Time management and delays
- **Must be imported with `use('time')`**
- No `time.now()` function - use:
- `time.number()` - Number representation of current time
- `time.record()` - Struct representation of current time
- `time.text()` - Text representation of current time
- `io` - File I/O operations - `io` - File I/O operations
- `json` - JSON parsing and serialization - `json` - JSON parsing and serialization
- `util` - General utilities - `util` - General utilities
@@ -234,7 +239,7 @@ When sending a message with a callback, respond by sending to the message itself
```javascript ```javascript
// Sender side: // Sender side:
send(actor, {type: 'status'}, response => { send(actor, {type: 'status'}, response => {
console.log(response); // Handle the response log.console(response); // Handle the response
}); });
// Receiver side: // Receiver side:
@@ -279,7 +284,7 @@ $_.receiver(msg => {
- Custom formats: Aseprite animations, etc. - Custom formats: Aseprite animations, etc.
### Developer Tools ### Developer Tools
- Built-in documentation system with `prosperon.DOC` - Built-in documentation system with `cell.DOC`
- Tracy profiler integration for performance monitoring - Tracy profiler integration for performance monitoring
- Imgui debugging tools - Imgui debugging tools
- Console logging with various severity levels - Console logging with various severity levels

View File

@@ -1,22 +1,22 @@
debug: FORCE debug: FORCE
meson setup build_dbg -Dbuildtype=debug meson setup build_dbg -Dbuildtype=debug
meson compile -C build_dbg meson install --only-changed -C build_dbg
fast: FORCE fast: FORCE
meson setup build_fast meson setup build_fast
meson compile -C build_fast meson install -C build_fast
release: FORCE release: FORCE
meson setup -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true build_release meson setup -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true build_release
meson compile -C build_release meson install -C build_release
sanitize: FORCE sanitize: FORCE
meson setup -Db_sanitize=address -Db_sanitize=memory -Db_sanitize=leak -Db_sanitize=undefined build_sani meson setup -Db_sanitize=address -Db_sanitize=memory -Db_sanitize=leak -Db_sanitize=undefined build_sani
meson compile -C build_sani meson install -C build_sani
small: FORCE small: FORCE
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
meson compile -C build_small meson install -C build_small
web: FORCE web: FORCE
meson setup -Deditor=false -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true --cross-file emscripten.cross build_web meson setup -Deditor=false -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true --cross-file emscripten.cross build_web

View File

@@ -49,28 +49,28 @@ function getStats(arr) {
} }
// Pretty print results // Pretty print results
console.log("\n=== Performance Test Results (100 iterations) ==="); log.console("\n=== Performance Test Results (100 iterations) ===");
console.log("\nJSON Decoding (ms):"); log.console("\nJSON Decoding (ms):");
const jsonDecStats = getStats(jsonDecodeTimes); const jsonDecStats = getStats(jsonDecodeTimes);
console.log(`Average: ${jsonDecStats.avg.toFixed(2)} ms`); log.console(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
console.log(`Min: ${jsonDecStats.min.toFixed(2)} ms`); log.console(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
console.log(`Max: ${jsonDecStats.max.toFixed(2)} ms`); log.console(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
console.log("\nJSON Encoding (ms):"); log.console("\nJSON Encoding (ms):");
const jsonEncStats = getStats(jsonEncodeTimes); const jsonEncStats = getStats(jsonEncodeTimes);
console.log(`Average: ${jsonEncStats.avg.toFixed(2)} ms`); log.console(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
console.log(`Min: ${jsonEncStats.min.toFixed(2)} ms`); log.console(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
console.log(`Max: ${jsonEncStats.max.toFixed(2)} ms`); log.console(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
console.log("\nNOTA Encoding (ms):"); log.console("\nNOTA Encoding (ms):");
const notaEncStats = getStats(notaEncodeTimes); const notaEncStats = getStats(notaEncodeTimes);
console.log(`Average: ${notaEncStats.avg.toFixed(2)} ms`); log.console(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
console.log(`Min: ${notaEncStats.min.toFixed(2)} ms`); log.console(`Min: ${notaEncStats.min.toFixed(2)} ms`);
console.log(`Max: ${notaEncStats.max.toFixed(2)} ms`); log.console(`Max: ${notaEncStats.max.toFixed(2)} ms`);
console.log("\nNOTA Decoding (ms):"); log.console("\nNOTA Decoding (ms):");
const notaDecStats = getStats(notaDecodeTimes); const notaDecStats = getStats(notaDecodeTimes);
console.log(`Average: ${notaDecStats.avg.toFixed(2)} ms`); log.console(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
console.log(`Min: ${notaDecStats.min.toFixed(2)} ms`); log.console(`Min: ${notaDecStats.min.toFixed(2)} ms`);
console.log(`Max: ${notaDecStats.max.toFixed(2)} ms`); log.console(`Max: ${notaDecStats.max.toFixed(2)} ms`);

View File

@@ -75,8 +75,8 @@ const benchmarks = [
]; ];
// Print a header // Print a header
console.log("Wota Encode/Decode Benchmark"); log.console("Wota Encode/Decode Benchmark");
console.log("============================\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) {
@@ -96,11 +96,11 @@ for (let bench of benchmarks) {
let elapsedSec = measureTime(runAllData, bench.iterations); let elapsedSec = measureTime(runAllData, bench.iterations);
let opsPerSec = (totalIterations / elapsedSec).toFixed(1); let opsPerSec = (totalIterations / elapsedSec).toFixed(1);
console.log(`${bench.name}:`); log.console(`${bench.name}:`);
console.log(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`); log.console(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
console.log(` Elapsed: ${elapsedSec.toFixed(3)} s`); log.console(` Elapsed: ${elapsedSec.toFixed(3)} s`);
console.log(` Throughput: ${opsPerSec} encode+decode ops/sec\n`); log.console(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
} }
// All done // All done
console.log("Benchmark completed.\n"); log.console("Benchmark completed.\n");

View File

@@ -53,6 +53,11 @@ const libraries = [
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const benchmarks = [ const benchmarks = [
{
name: "Empty object",
data: [{}, {}, {}, {}],
iterations: 10000
},
{ {
name: "Small Integers", name: "Small Integers",
data: [0, 42, -1, 2023], data: [0, 42, -1, 2023],
@@ -125,8 +130,8 @@ function runBenchmarkForLibrary(lib, bench) {
let encodeTime = measureTime(() => { let encodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) { for (let i = 0; i < bench.iterations; i++) {
// For each data item, encode it // For each data item, encode it
for (let d of bench.data) { for (let j = 0; j < bench.data.length; j++) {
let e = lib.encode(d); 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) {
@@ -155,12 +160,12 @@ function runBenchmarkForLibrary(lib, bench) {
// 5. Main driver: run across all benchmarks, for each library. // 5. Main driver: run across all benchmarks, for each library.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
console.log("Benchmark: Wota vs Nota vs JSON"); log.console("Benchmark: Wota vs Nota vs JSON");
console.log("================================\n"); log.console("================================\n");
for (let bench of benchmarks) { for (let bench of benchmarks) {
console.log(`SCENARIO: ${bench.name}`); log.console(`SCENARIO: ${bench.name}`);
console.log(` Data length: ${bench.data.length} | Iterations: ${bench.iterations}\n`); log.console(` Data length: ${bench.data.length} | Iterations: ${bench.iterations}\n`);
for (let lib of libraries) { for (let lib of libraries) {
let { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench); let { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
@@ -170,13 +175,15 @@ for (let bench of benchmarks) {
let encOpsPerSec = (totalOps / encodeTime).toFixed(1); let encOpsPerSec = (totalOps / encodeTime).toFixed(1);
let decOpsPerSec = (totalOps / decodeTime).toFixed(1); let decOpsPerSec = (totalOps / decodeTime).toFixed(1);
console.log(` ${lib.name}:`); log.console(` ${lib.name}:`);
console.log(` Encode time: ${encodeTime.toFixed(3)}s => ${encOpsPerSec} encodes/sec`); log.console(` Encode time: ${encodeTime.toFixed(3)}s => ${encOpsPerSec} encodes/sec [${(encodeTime/bench.iterations)*1000000000} ns/try]`);
console.log(` Decode time: ${decodeTime.toFixed(3)}s => ${decOpsPerSec} decodes/sec`); log.console(` Decode time: ${decodeTime.toFixed(3)}s => ${decOpsPerSec} decodes/sec [${(decodeTime/bench.iterations)*1000000000}/try]`);
console.log(` Total size: ${totalSize} bytes (or code units for JSON)`); log.console(` Total size: ${totalSize} bytes (or code units for JSON)`);
console.log(""); log.console("");
} }
console.log("---------------------------------------------------------\n"); log.console("---------------------------------------------------------\n");
} }
console.log("Benchmark complete.\n"); log.console("Benchmark complete.\n");
os.exit()

View File

@@ -47,7 +47,7 @@ Certain functions are intrinsic to the program and cannot be overridden. They
- **Example**: - **Example**:
```js ```js
this.delay(_ => { this.delay(_ => {
console.log("3 seconds later!") log.console("3 seconds later!")
}, 3) }, 3)
``` ```

View File

@@ -3,11 +3,11 @@
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object". Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".
Docstrings are set to the symbol `prosperon.DOC` Docstrings are set to the symbol `cell.DOC`
```js ```js
// Suppose we have a module that returns a function // Suppose we have a module that returns a function
function greet(name) { console.log("Hello, " + name) } function greet(name) { log.console("Hello, " + name) }
// We can attach a docstring // We can attach a docstring
greet.doc = ` greet.doc = `
@@ -21,12 +21,12 @@ return greet
```js ```js
// Another way is to add a docstring object to an object // Another way is to add a docstring object to an object
var greet = { var greet = {
hello() { console.log('hello!') } hello() { log.console('hello!') }
} }
greet[prosperon.DOC] = {} greet[cell.DOC] = {}
greet[prosperon.DOC][prosperon.DOC] = 'An object full of different greeter functions' greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
greet[prosperon.DOC].hello = 'A greeter that says, "hello!"' greet[cell.DOC].hello = 'A greeter that says, "hello!"'
``` ```

View File

@@ -49,7 +49,7 @@ return {
This will cause prosperon to launch a 500x500 window with the title 'Hello World'. In your ```main.js```, write the following: This will cause prosperon to launch a 500x500 window with the title 'Hello World'. In your ```main.js```, write the following:
``` ```
console.log("Hello world") log.console("Hello world")
this.delay(_ => { this.delay(_ => {
this.kill(); this.kill();
@@ -62,6 +62,6 @@ this.delay(_ => {
The global object called `prosperon` has a variety of engine specific settings on it that can be set to influence how the engine behaves. For example, `prosperon.argv` contains a list of the command line arguments given to prosperon; `prosperon.PATH` is an array of paths to resolve resources such as modules and images. `prosperon` is fully documented in the API section. The global object called `prosperon` has a variety of engine specific settings on it that can be set to influence how the engine behaves. For example, `prosperon.argv` contains a list of the command line arguments given to prosperon; `prosperon.PATH` is an array of paths to resolve resources such as modules and images. `prosperon` is fully documented in the API section.
## Getting help ## Getting help
The `prosperon` global has a 'doc' function, which can be invoked on any engine object to see a description of it and its members. For example, to learn about `prosperon`, try printing out `prosperon.doc(prosperon)` in your `main.js`. The `prosperon` global has a 'doc' function, which can be invoked on any engine object to see a description of it and its members. For example, to learn about `prosperon`, try printing out `cell.DOC(prosperon)` in your `main.js`.
Writing documentation for your own modules and game components will be explored in the chapter on actors & modules. Writing documentation for your own modules and game components will be explored in the chapter on actors & modules.

View File

@@ -101,7 +101,7 @@ $_.receiver(function(msg) {
break; break;
case 'status': case 'status':
console.log(`got status request. current is ${json.encode(get_status())}`) log.console(`got status request. current is ${json.encode(get_status())}`)
send(msg, { send(msg, {
type: 'status_response', type: 'status_response',
...get_status() ...get_status()

View File

@@ -8,13 +8,13 @@ var waiting_client = null;
var match_id = 0; var match_id = 0;
$_.portal(e => { $_.portal(e => {
console.log("NAT server: received connection request"); log.console("NAT server: received connection request");
if (!is_actor(e.actor)) if (!is_actor(e.actor))
send(e, {reason: "Must provide the actor you want to connect."}); send(e, {reason: "Must provide the actor you want to connect."});
if (waiting_client) { if (waiting_client) {
console.log(`sending out messages! to ${json.encode(e.actor)} and ${json.encode(waiting_client.actor)}`) log.console(`sending out messages! to ${json.encode(e.actor)} and ${json.encode(waiting_client.actor)}`)
send(waiting_client, e.actor) send(waiting_client, e.actor)
send(e, waiting_client.actor) send(e, waiting_client.actor)
@@ -25,5 +25,5 @@ $_.portal(e => {
waiting_client = e waiting_client = e
console.log(`actor ${json.encode(e.actor)} is waiting ...`) log.console(`actor ${json.encode(e.actor)} is waiting ...`)
}, 4000); }, 4000);

View File

@@ -1,11 +1,11 @@
console.log(`nat client starting`) log.console(`nat client starting`)
$_.contact((actor, reason) => { $_.contact((actor, reason) => {
if (actor) { if (actor) {
console.log(`trying to message ${json.encode(actor)}`) log.console(`trying to message ${json.encode(actor)}`)
send(actor, {type:"greet"}) send(actor, {type:"greet"})
} else { } else {
console.log(json.encode(reason)) log.console(json.encode(reason))
} }
}, { }, {
address: "108.210.60.32", // NAT server's public IP address: "108.210.60.32", // NAT server's public IP
@@ -16,7 +16,7 @@ $_.contact((actor, reason) => {
$_.receiver(e => { $_.receiver(e => {
switch(e.type) { switch(e.type) {
case 'greet': case 'greet':
console.log(`hello!`) log.console(`hello!`)
break break
} }
}) })

View File

@@ -1,7 +1,8 @@
project('prosperon', ['c', 'cpp'], project('cell', ['c', 'cpp'],
version: '0.9.3', version: '0.9.3',
meson_version: '>=1.4', meson_version: '>=1.4',
default_options : [ 'cpp_std=c++11']) default_options : [ 'cpp_std=c++11']
)
libtype = get_option('default_library') libtype = get_option('default_library')
@@ -13,21 +14,21 @@ fs = import('fs')
add_project_arguments('-pedantic', language: ['c']) add_project_arguments('-pedantic', language: ['c'])
git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false) git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false)
prosperon_version = 'unknown' cell_version = 'unknown'
if git_tag_cmd.returncode() == 0 if git_tag_cmd.returncode() == 0
prosperon_version = git_tag_cmd.stdout().strip() cell_version = git_tag_cmd.stdout().strip()
endif endif
git_commit_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false) git_commit_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false)
prosperon_commit = 'unknown' cell_commit = 'unknown'
if git_commit_cmd.returncode() == 0 if git_commit_cmd.returncode() == 0
prosperon_commit = git_commit_cmd.stdout().strip() cell_commit = git_commit_cmd.stdout().strip()
endif endif
# Important: pass the definitions without double-escaping quotes # Pass version/commit defines to C
add_project_arguments( add_project_arguments(
'-DPROSPERON_VERSION="' + prosperon_version + '"', '-DCELL_VERSION="' + cell_version + '"',
'-DPROSPERON_COMMIT="' + prosperon_commit + '"', '-DCELL_COMMIT="' + cell_commit + '"',
language : 'c' language : 'c'
) )
@@ -67,22 +68,32 @@ endif
cmake = import('cmake') cmake = import('cmake')
mbedtls_opts = cmake.subproject_options() # mbedtls (either system or subproject)
mbedtls_opts.add_cmake_defines({ mbedtls_dep = dependency('mbedtls', static: true, required: false)
'ENABLE_PROGRAMS': 'OFF', # Disable Mbed TLS programs mbedx509_dep = dependency('mbedx509', static: true, required: false)
'ENABLE_TESTING': 'OFF', # Disable Mbed TLS tests mbedcrypto_dep = dependency('mbedcrypto', static: true, required: false)
'CMAKE_BUILD_TYPE': 'Release', # Optimize for release if not mbedtls_dep.found() or not mbedx509_dep.found() or not mbedcrypto_dep.found()
'MBEDTLS_FATAL_WARNINGS': 'ON', # Treat warnings as errors message('⚙ System mbedtls not found, building subproject...')
'USE_STATIC_MBEDTLS_LIBRARY': 'ON',# Build static libraries mbedtls_opts = cmake.subproject_options()
'USE_SHARED_MBEDTLS_LIBRARY': 'OFF'# Disable shared libraries mbedtls_opts.add_cmake_defines({
}) 'ENABLE_PROGRAMS': 'OFF',
mbedtls_proj = cmake.subproject('mbedtls', options: mbedtls_opts) 'ENABLE_TESTING': 'OFF',
deps += [ 'CMAKE_BUILD_TYPE': 'Release',
mbedtls_proj.dependency('mbedtls'), 'MBEDTLS_FATAL_WARNINGS': 'ON',
mbedtls_proj.dependency('mbedx509'), 'USE_STATIC_MBEDTLS_LIBRARY': 'ON',
mbedtls_proj.dependency('mbedcrypto') 'USE_SHARED_MBEDTLS_LIBRARY': 'OFF'
] })
mbedtls_proj = cmake.subproject('mbedtls', options: mbedtls_opts)
deps += [
mbedtls_proj.dependency('mbedtls'),
mbedtls_proj.dependency('mbedx509'),
mbedtls_proj.dependency('mbedcrypto')
]
else
deps += [mbedtls_dep, mbedx509_dep, mbedcrypto_dep]
endif
# SDL3 (system or subproject)
sdl3_opts = cmake.subproject_options() sdl3_opts = cmake.subproject_options()
sdl3_opts.add_cmake_defines({ sdl3_opts.add_cmake_defines({
'SDL_STATIC': 'ON', 'SDL_STATIC': 'ON',
@@ -93,77 +104,111 @@ sdl3_opts.add_cmake_defines({
'SDL_PIPEWIRE': 'ON', 'SDL_PIPEWIRE': 'ON',
'SDL_PULSEAUDIO': 'ON', 'SDL_PULSEAUDIO': 'ON',
}) })
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
if host_machine.system() == 'darwin'
deps += dependency('appleframeworks', modules: 'accelerate')
add_project_arguments('-DACCELERATE_NEW_LAPACK=1', language:'c')
add_project_arguments('-DACCELERATE_LAPACK_ILP64=1', language:'c')
endif
if host_machine.system() == 'linux'
deps += cc.find_library('asound', required:true)
deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')]
endif
if host_machine.system() == 'windows'
deps += cc.find_library('d3d11')
deps += cc.find_library('ws2_32', required:true)
deps += cc.find_library('dbghelp')
deps += cc.find_library('winmm')
deps += cc.find_library('setupapi')
deps += cc.find_library('imm32')
deps += cc.find_library('version')
deps += cc.find_library('cfgmgr32')
deps += cc.find_library('bcrypt')
sdl3_opts.add_cmake_defines({'HAVE_ISINF': '1'}) # Hack for MSYS2
sdl3_opts.add_cmake_defines({'HAVE_ISNAN': '1'})
link += '-static'
endif
if host_machine.system() == 'emscripten' if host_machine.system() == 'emscripten'
link += '-sUSE_WEBGPU' link += '-sUSE_WEBGPU'
# Use the pre-installed copy deps += dependency('sdl3', static : true, method : 'pkg-config', required : true)
deps += dependency('sdl3',
static : true,
method : 'pkg-config', # or 'cmake' if you prefer
required : true)
else else
sdl3_proj = cmake.subproject('sdl3', options : sdl3_opts) sdl3_dep = dependency('sdl3', static: true, required: false)
deps += sdl3_proj.dependency('SDL3-static') if not sdl3_dep.found()
message('⚙ System SDL3 not found, building subproject...')
sdl3_proj = cmake.subproject('sdl3', options : sdl3_opts)
deps += sdl3_proj.dependency('SDL3-static')
else
deps += sdl3_dep
endif
endif endif
quickjs_opts = [] quickjs_opts = []
quickjs_opts += 'default_library=static' quickjs_opts += 'default_library=static'
deps += dependency('quickjs', static:true, default_options:quickjs_opts) if get_option('buildtype') != 'release'
deps += dependency('qjs-layout', static:true) quickjs_opts += 'leaks=true'
deps += dependency('qjs-miniz', static:true) endif
deps += dependency('physfs', static:true) 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
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)
if not miniz_dep.found()
message('⚙ System miniz not found, building subproject...')
deps += dependency('miniz', static:true)
else
deps += miniz_dep
endif
physfs_dep = dependency('physfs', static: true, required: false)
if not physfs_dep.found()
message('⚙ System physfs not found, building subproject...')
deps += dependency('physfs', static:true)
else
deps += physfs_dep
endif
deps += dependency('threads') deps += dependency('threads')
deps += dependency('chipmunk', static:true)
chipmunk_dep = dependency('chipmunk', static: true, required: false)
if not chipmunk_dep.found()
message('⚙ System chipmunk not found, building subproject...')
deps += dependency('chipmunk', static:true)
else
deps += chipmunk_dep
endif
if host_machine.system() != 'emscripten' if host_machine.system() != 'emscripten'
deps += dependency('enet', static:true) enet_dep = dependency('enet', static: true, required: false)
if not enet_dep.found()
message('⚙ System enet not found, building subproject...')
deps += dependency('enet', static:true)
else
deps += enet_dep
endif
src += 'qjs_enet.c' src += 'qjs_enet.c'
src += 'qjs_tracy.c' tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true']
tracy_opts = ['fibers=true', 'on_demand=true']
add_project_arguments('-DTRACY_ENABLE', language:['c','cpp']) add_project_arguments('-DTRACY_ENABLE', language:['c','cpp'])
deps += dependency('tracy', static:true, default_options:tracy_opts) tracy_dep = dependency('tracy', static: true, required: false)
if not tracy_dep.found()
message('⚙ System tracy not found, building subproject...')
deps += dependency('tracy', static:true, default_options:tracy_opts)
else
deps += tracy_dep
endif
src += 'qjs_dmon.c' src += 'qjs_dmon.c'
endif endif
deps += dependency('soloud', static:true) soloud_dep = dependency('soloud', static: true, required: false)
deps += dependency('libqrencode', static:true) if not soloud_dep.found()
message('⚙ System soloud not found, building subproject...')
deps += dependency('soloud', static:true)
else
deps += soloud_dep
endif
qr_dep = dependency('qrencode', static: true, required: false)
if not qr_dep.found()
message('⚙ System qrencode not found, building subproject...')
deps += dependency('libqrencode', static:true)
else
deps += qr_dep
endif
# Storefront SDK support
storefront = get_option('storefront') storefront = get_option('storefront')
if storefront == 'steam' if storefront == 'steam'
steam_sdk_path = meson.current_source_dir() / 'sdk' steam_sdk_path = meson.current_source_dir() / 'sdk'
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'
steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'osx' / 'libsteam_api.dylib' steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'osx' / 'libsteam_api.dylib'
elif host_machine.system() == 'linux' elif host_machine.system() == 'linux'
@@ -173,7 +218,7 @@ if storefront == 'steam'
else else
steam_lib_path = '' steam_lib_path = ''
endif endif
if fs.exists(steam_lib_path) if fs.exists(steam_lib_path)
steam_dep = declare_dependency( steam_dep = declare_dependency(
include_directories: include_directories('sdk/public'), include_directories: include_directories('sdk/public'),
@@ -190,15 +235,17 @@ else
message('Storefront: ' + storefront) message('Storefront: ' + storefront)
endif endif
# Any extra linker flags
link_args = link link_args = link
# === COLLECT ALL SOURCE FILES ===
sources = [] 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','prosperon.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_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_os.c', 'qjs_actor.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',
'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' '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'
] ]
# quirc src
src += [ src += [
'thirdparty/quirc/quirc.c', 'thirdparty/quirc/decode.c', 'thirdparty/quirc/quirc.c', 'thirdparty/quirc/decode.c',
'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c' 'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c'
@@ -223,101 +270,49 @@ foreach file : src
sources += files(full_path) sources += files(full_path)
endforeach endforeach
if get_option('editor')
# sources += 'source/qjs_imgui.cpp'
# foreach imgui : imsrc
# sources += tp / 'imgui' / imgui
# endforeach
endif
includers = [] includers = []
foreach inc : includes foreach inc : includes
includers += include_directories(inc) includers += include_directories(inc)
endforeach endforeach
zip_folders = ['scripts', 'fonts', 'icons', 'shaders']
zip_paths = []
foreach folder: zip_folders
zip_paths += meson.project_source_root() / folder
endforeach
# Produce core.zip
core = custom_target('core.zip',
output : 'core.zip',
command : ['sh', '-c',
'cd ' + meson.project_source_root() +
' && echo "Rebuilding core.zip" && rm -f ' + meson.current_build_dir() + '/core.zip && ' +
'zip -r ' + meson.current_build_dir() + '/core.zip scripts fonts icons shaders'
],
build_always_stale: true,
build_by_default: true
)
prosperon_raw = executable('prosperon_raw', sources,
dependencies: deps,
include_directories: includers,
link_args: link,
build_rpath: '$ORIGIN',
install:false
)
strip_enabled = ['release', 'minsize'].contains(get_option('buildtype'))
if strip_enabled
prosperon_raw_stripped = custom_target('prosperon_raw_stripped',
input: prosperon_raw,
output: 'prosperon_raw_stripped',
command: [
'sh', '-c',
'strip "$1" && cp "$1" "$2"',
'stripper',
'@INPUT@',
'@OUTPUT@'
],
build_by_default: true
)
exe_for_concat = prosperon_raw_stripped
else
exe_for_concat = prosperon_raw
endif
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
exe_ext = '.exe' exe_ext = '.exe'
else else
exe_ext = '' exe_ext = ''
endif endif
prosperon = custom_target('prosperon', strip_enabled = ['release', 'minsize'].contains(get_option('buildtype'))
output: 'prosperon' + exe_ext, if strip_enabled
input: [exe_for_concat, core], add_project_link_arguments('-s', language: ['c', 'cpp'])
command: [ endif
'sh', '-c',
'cat "$1" "$2" > "$3" && chmod +x "$3" >/dev/null 2>&1', # === NEW: build a STATIC library "cell_core" from exactly the same sources ===
'concat', cell_core = static_library(
'@INPUT0@', 'cell_core', # → libcell_core.a
'@INPUT1@', sources,
'@OUTPUT@' dependencies: deps,
], include_directories: includers
build_always_stale: true,
build_by_default: true
) )
prosperon_dep = declare_dependency( # === Now build the "cell" executable by linking against that static lib ===
link_with: prosperon cell = executable('cell',
link_with : [ cell_core ],
dependencies : deps,
include_directories: includers,
link_args : link_args,
build_rpath : '$ORIGIN',
install : true
) )
copy_tests = custom_target( # === Declare a Meson dependency so that other subprojects can do `dependency('cell')` if needed ===
'copy_tests', cell_dep = declare_dependency(
output: 'tests', link_with: [ cell_core ],
command: [ include_directories: includers
'cp', '-rf',
join_paths(meson.project_source_root(), 'tests'),
meson.project_build_root()
],
build_always_stale: true,
build_by_default: true
) )
# === TESTS (unchanged) ===
tests = [ tests = [
'spawn_actor', 'spawn_actor',
'empty', 'empty',
@@ -328,7 +323,6 @@ tests = [
'send', 'send',
'delay' 'delay'
] ]
foreach file : tests foreach file : tests
test(file, prosperon_raw, args:['tests/' + file + '.js'], depends:copy_tests) test(file, cell, args:['tests/' + file])
endforeach endforeach

827
prosperon/_sdl_video.ce Normal file
View File

@@ -0,0 +1,827 @@
// SDL Video Actor
// This actor runs on the main thread and handles all SDL video operations
log.console("TO HERE")
var surface = use('surface')
// Default window configuration - documents all available window options
var default_window = {
// Basic properties
title: "Prosperon Window",
width: 640,
height: 480,
// Position - can be numbers or "centered"
x: undefined, // SDL_WINDOWPOS_UNDEFINED by default
y: undefined, // SDL_WINDOWPOS_UNDEFINED by default
// Window behavior flags
resizable: true,
fullscreen: false,
hidden: false,
borderless: false,
alwaysOnTop: false,
minimized: false,
maximized: false,
// Input grabbing
mouseGrabbed: false,
keyboardGrabbed: false,
// Display properties
highPixelDensity: false,
transparent: false,
opacity: 1.0, // 0.0 to 1.0
// Focus behavior
notFocusable: false,
// Special window types (mutually exclusive)
utility: false, // Utility window (not in taskbar)
tooltip: false, // Tooltip window (requires parent)
popupMenu: false, // Popup menu window (requires parent)
// Graphics API flags (let SDL choose if not specified)
opengl: false, // Force OpenGL context
vulkan: false, // Force Vulkan context
metal: false, // Force Metal context (macOS)
// Advanced properties
parent: undefined, // Parent window for tooltips/popups/modal
modal: false, // Modal to parent window (requires parent)
externalGraphicsContext: false, // Use external graphics context
// Input handling
textInput: true, // Enable text input on creation
};
// Resource tracking
var resources = {
window: {},
renderer: {},
texture: {},
surface: {},
cursor: {}
};
// ID counter for resource allocation
var next_id = 1;
// Helper to allocate new ID
function allocate_id() {
return next_id++;
}
// Message handler
$_.receiver(function(msg) {
if (!msg.kind || !msg.op) {
send(msg, {error: "Message must have 'kind' and 'op' fields"});
return;
}
var response = {};
try {
switch (msg.kind) {
case 'window':
response = handle_window(msg);
break;
case 'renderer':
response = handle_renderer(msg);
break;
case 'texture':
response = handle_texture(msg);
break;
case 'surface':
response = handle_surface(msg);
break;
case 'cursor':
response = handle_cursor(msg);
break;
case 'mouse':
response = handle_mouse(msg);
break;
case 'keyboard':
response = handle_keyboard(msg);
break;
default:
response = {error: "Unknown kind: " + msg.kind};
}
} catch (e) {
response = {error: e.toString()};
log.error(e)
}
send(msg, response);
});
// Window operations
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) {
case 'destroy':
win.destroy();
delete resources.window[msg.id];
return {success: true};
case 'show':
win.visible = true;
return {success: true};
case 'hide':
win.visible = false;
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 === 'surface') {
var surf = win.surface;
if (!surf) return {data: null};
var surf_id = allocate_id();
resources.surface[surf_id] = surf;
return {data: surf_id};
}
return {data: win[prop]};
case 'set':
var prop = msg.data ? msg.data.property : null;
var value = msg.data ? msg.data.value : undefined;
if (!prop) return {error: "Missing property name"};
// Validate property is settable
var readonly = ['id', 'pixelDensity', 'displayScale', 'sizeInPixels', 'flags', 'surface'];
if (readonly.indexOf(prop) !== -1) {
return {error: "Property '" + prop + "' is read-only"};
}
win[prop] = value;
return {success: true};
case 'fullscreen':
win.fullscreen();
return {success: true};
case 'updateSurface':
win.updateSurface();
return {success: true};
case 'updateSurfaceRects':
if (!msg.data || !msg.data.rects) return {error: "Missing rects array"};
win.updateSurfaceRects(msg.data.rects);
return {success: true};
case 'raise':
win.raise();
return {success: true};
case 'restore':
win.restore();
return {success: true};
case 'flash':
win.flash(msg.data ? msg.data.operation : 'briefly');
return {success: true};
case 'sync':
win.sync();
return {success: true};
case 'setIcon':
if (!msg.data || !msg.data.surface_id) return {error: "Missing surface_id"};
var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"};
win.set_icon(surf);
return {success: true};
case 'makeRenderer':
var renderer = win.make_renderer();
var renderer_id = allocate_id();
resources.renderer[renderer_id] = renderer;
return {id: renderer_id};
default:
return {error: "Unknown window operation: " + msg.op};
}
}
// Renderer operations
function handle_renderer(msg) {
// Special case: createWindowAndRenderer creates both
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;
return {success: true};
case 'line':
if (!msg.data || !msg.data.points) return {error: "Missing points array"};
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
function handle_texture(msg) {
// Special case: create needs a renderer
if (msg.op === 'create') {
if (!msg.data) return {error: "Missing texture data"};
var ren_id = msg.data.renderer_id;
if (!ren_id || !resources.renderer[ren_id]) return {error: "Invalid renderer id"};
var tex;
var renderer = resources.renderer[ren_id];
// Create from surface
if (msg.data.surface_id) {
var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"};
tex = new prosperon.endowments.texture(renderer, surf);
}
// Create from properties
else if (msg.data.width && msg.data.height) {
tex = new prosperon.endowments.texture(renderer, {
width: msg.data.width,
height: msg.data.height,
format: msg.data.format || 'rgba8888',
pixels: msg.data.pixels,
pitch: msg.data.pitch
});
}
else {
log.console(json.encode(msg.data))
return {error: "Must provide either surface_id or width/height"};
}
var tex_id = allocate_id();
resources.texture[tex_id] = tex;
return {id: tex_id, data: {size: tex.size}};
}
// All other operations require a valid texture ID
if (!msg.id || !resources.texture[msg.id]) {
return {error: "Invalid texture id: " + msg.id};
}
var tex = resources.texture[msg.id];
switch (msg.op) {
case 'destroy':
delete resources.texture[msg.id];
// Texture is automatically destroyed when all references are gone
return {success: true};
case 'get':
var prop = msg.data ? msg.data.property : null;
if (!prop) return {error: "Missing property name"};
return {data: tex[prop]};
case 'set':
var prop = msg.data ? msg.data.property : null;
var value = msg.data ? msg.data.value : undefined;
if (!prop) return {error: "Missing property name"};
// Validate property is settable
var readonly = ['size', 'width', 'height'];
if (readonly.indexOf(prop) !== -1) {
return {error: "Property '" + prop + "' is read-only"};
}
tex[prop] = value;
return {success: true};
case 'update':
if (!msg.data) return {error: "Missing update data"};
tex.update(
msg.data.rect || null,
msg.data.pixels,
msg.data.pitch || 0
);
return {success: true};
case 'lock':
var result = tex.lock(msg.data ? msg.data.rect : null);
return {data: result};
case 'unlock':
tex.unlock();
return {success: true};
case 'query':
return {data: tex.query()};
default:
return {error: "Unknown texture operation: " + msg.op};
}
}
// Surface operations (mainly for cleanup)
function handle_surface(msg) {
switch (msg.op) {
case 'destroy':
if (!msg.id || !resources.surface[msg.id]) {
return {error: "Invalid surface id: " + msg.id};
}
delete resources.surface[msg.id];
return {success: true};
default:
return {error: "Unknown surface operation: " + msg.op};
}
}
// Cursor operations
function handle_cursor(msg) {
switch (msg.op) {
case 'create':
var surf = new surface(msg.data)
var hotspot = msg.data.hotspot || [0, 0];
var cursor = prosperon.endowments.createCursor(surf, hotspot);
var cursor_id = allocate_id();
resources.cursor[cursor_id] = cursor;
return {id: cursor_id};
case 'set':
var cursor = null;
if (msg.id && resources.cursor[msg.id]) {
cursor = resources.cursor[msg.id];
}
prosperon.endowments.setCursor(cursor);
return {success: true};
case 'destroy':
if (!msg.id || !resources.cursor[msg.id]) {
return {error: "Invalid cursor id: " + msg.id};
}
delete resources.cursor[msg.id];
return {success: true};
default:
return {error: "Unknown cursor operation: " + msg.op};
}
}
// Utility function to create window and renderer
prosperon.endowments = prosperon.endowments || {};
// Mouse operations
function handle_mouse(msg) {
var mouse = prosperon.endowments.mouse;
switch (msg.op) {
case 'show':
if (msg.data === undefined) return {error: "Missing show parameter"};
mouse.show(msg.data);
return {success: true};
case 'capture':
if (msg.data === undefined) return {error: "Missing capture parameter"};
mouse.capture(msg.data);
return {success: true};
case 'get_state':
return {data: mouse.get_state()};
case 'get_global_state':
return {data: mouse.get_global_state()};
case 'get_relative_state':
return {data: mouse.get_relative_state()};
case 'warp_global':
if (!msg.data) return {error: "Missing position"};
mouse.warp_global(msg.data);
return {success: true};
case 'warp_in_window':
if (!msg.data || !msg.data.window_id || !msg.data.pos)
return {error: "Missing window_id or position"};
var window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
mouse.warp_in_window(window, msg.data.pos);
return {success: true};
case 'cursor_visible':
return {data: mouse.cursor_visible()};
case 'get_cursor':
var cursor = mouse.get_cursor();
if (!cursor) return {data: null};
// Find or create cursor ID
for (var id in resources.cursor) {
if (resources.cursor[id] === cursor) {
return {data: id};
}
}
// Not tracked, add it
var cursor_id = allocate_id();
resources.cursor[cursor_id] = cursor;
return {data: cursor_id};
case 'get_default_cursor':
var cursor = mouse.get_default_cursor();
if (!cursor) return {data: null};
// Find or create cursor ID
for (var id in resources.cursor) {
if (resources.cursor[id] === cursor) {
return {data: id};
}
}
// Not tracked, add it
var cursor_id = allocate_id();
resources.cursor[cursor_id] = cursor;
return {data: cursor_id};
case 'create_system_cursor':
if (msg.data === undefined) return {error: "Missing cursor type"};
var cursor = mouse.create_system_cursor(msg.data);
var cursor_id = allocate_id();
resources.cursor[cursor_id] = cursor;
return {id: cursor_id};
case 'get_focus':
var window = mouse.get_focus();
if (!window) return {data: null};
// Find window ID
for (var id in resources.window) {
if (resources.window[id] === window) {
return {data: id};
}
}
// Not tracked, add it
var win_id = allocate_id();
resources.window[win_id] = window;
return {data: win_id};
default:
return {error: "Unknown mouse operation: " + msg.op};
}
}
// Keyboard operations
function handle_keyboard(msg) {
var keyboard = prosperon.endowments.keyboard;
switch (msg.op) {
case 'get_state':
return {data: keyboard.get_state()};
case 'get_focus':
var window = keyboard.get_focus();
if (!window) return {data: null};
// Find window ID
for (var id in resources.window) {
if (resources.window[id] === window) {
return {data: id};
}
}
// Not tracked, add it
var win_id = allocate_id();
resources.window[win_id] = window;
return {data: win_id};
case 'start_text_input':
var window = null;
if (msg.data && msg.data.window_id) {
window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
}
keyboard.start_text_input(window);
return {success: true};
case 'stop_text_input':
var window = null;
if (msg.data && msg.data.window_id) {
window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
}
keyboard.stop_text_input(window);
return {success: true};
case 'text_input_active':
var window = null;
if (msg.data && msg.data.window_id) {
window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
}
return {data: keyboard.text_input_active(window)};
case 'get_text_input_area':
var window = null;
if (msg.data && msg.data.window_id) {
window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
}
return {data: keyboard.get_text_input_area(window)};
case 'set_text_input_area':
if (!msg.data || !msg.data.rect) return {error: "Missing rect"};
var window = null;
if (msg.data.window_id) {
window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
}
keyboard.set_text_input_area(msg.data.rect, msg.data.cursor || 0, window);
return {success: true};
case 'clear_composition':
var window = null;
if (msg.data && msg.data.window_id) {
window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
}
keyboard.clear_composition(window);
return {success: true};
case 'screen_keyboard_shown':
if (!msg.data || !msg.data.window_id) return {error: "Missing window_id"};
var window = resources.window[msg.data.window_id];
if (!window) return {error: "Invalid window id"};
return {data: keyboard.screen_keyboard_shown(window)};
case 'reset':
keyboard.reset();
return {success: true};
default:
return {error: "Unknown keyboard operation: " + msg.op};
}
}

View File

@@ -1,6 +1,5 @@
var cam = {} var cam = {}
var os = use('os')
var transform = use('transform') var transform = use('transform')
var basecam = {} var basecam = {}

216
prosperon/color.cm Normal file
View File

@@ -0,0 +1,216 @@
function tohex(n) {
var s = Math.floor(n).toString(16);
if (s.length === 1) s = "0" + s;
return s.toUpperCase();
};
var Color = {
white: [1, 1, 1],
black: [0, 0, 0],
blue: [0, 0, 1],
green: [0, 1, 0],
yellow: [1, 1, 0],
red: [1, 0, 0],
gray: [0.71, 0.71, 0.71],
cyan: [0, 1, 1],
purple: [0.635, 0.365, 0.89],
orange: [1, 0.565, 0.251],
magenta: [1, 0, 1],
};
Color.editor = {};
Color.editor.ur = Color.green;
Color.tohtml = function (v) {
var html = v.map(function (n) {
return tohex(n * 255);
});
return "#" + html.join("");
};
var esc = {};
esc.reset = "\x1b[0";
esc.color = function (v) {
var c = v.map(function (n) {
return Math.floor(n * 255);
});
var truecolor = "\x1b[38;2;" + c.join(";") + ";";
return truecolor;
};
esc.doc = "Functions and constants for ANSI escape sequences.";
Color.Arkanoid = {
orange: [1, 0.561, 0],
teal: [0, 1, 1],
green: [0, 1, 0],
red: [1, 0, 0],
blue: [0, 0.439, 1],
purple: [1, 0, 1],
yellow: [1, 1, 0],
silver: [0.616, 0.616, 0.616],
gold: [0.737, 0.682, 0],
};
Color.Arkanoid.Powerups = {
red: [0.682, 0, 0] /* laser */,
blue: [0, 0, 0.682] /* enlarge */,
green: [0, 0.682, 0] /* catch */,
orange: [0.878, 0.561, 0] /* slow */,
purple: [0.824, 0, 0.824] /* break */,
cyan: [0, 0.682, 1] /* disruption */,
gray: [0.561, 0.561, 0.561] /* 1up */,
};
Color.Gameboy = {
darkest: [0.898, 0.42, 0.102],
dark: [0.898, 0.741, 0.102],
light: [0.741, 0.898, 0.102],
lightest: [0.42, 0.898, 0.102],
};
Color.Apple = {
green: [0.369, 0.741, 0.243],
yellow: [1, 0.725, 0],
orange: [0.969, 0.51, 0],
red: [0.886, 0.22, 0.22],
purple: [0.592, 0.224, 0.6],
blue: [0, 0.612, 0.875],
};
Color.Debug = {
boundingbox: Color.white,
names: [0.329, 0.431, 1],
};
Color.Editor = {
grid: [0.388, 1, 0.502],
select: [1, 1, 0.216],
newgroup: [0.471, 1, 0.039],
};
/* Detects the format of all colors and munges them into a floating point format */
Color.normalize = function (c) {
var add_a = function (a) {
var n = this.slice();
n[3] = a;
return n;
};
for (var p of Object.keys(c)) {
if (typeof c[p] !== "object") continue;
if (!Array.isArray(c[p])) {
Color.normalize(c[p]);
continue;
}
// Add alpha channel if not present
if (c[p].length === 3) {
c[p][3] = 1;
}
// Check if any values are > 1 (meaning they're in 0-255 format)
var needs_conversion = false;
for (var color of c[p]) {
if (color > 1) {
needs_conversion = true;
break;
}
}
// Convert from 0-255 to 0-1 if needed
if (needs_conversion) {
c[p] = c[p].map(function (x) {
return x / 255;
});
}
c[p].alpha = add_a;
}
};
Color.normalize(Color);
var ColorMap = {};
ColorMap.makemap = function (map) {
var newmap = Object.create(ColorMap);
Object.assign(newmap, map);
return newmap;
};
ColorMap.Jet = ColorMap.makemap({
0: [0, 0, 0.514],
0.125: [0, 0.235, 0.667],
0.375: [0.02, 1, 1],
0.625: [1, 1, 0],
0.875: [0.98, 0, 0],
1: [0.502, 0, 0],
});
ColorMap.BlueRed = ColorMap.makemap({
0: [0, 0, 1],
1: [1, 0, 0],
});
ColorMap.Inferno = ColorMap.makemap({
0: [0, 0, 0.016],
0.13: [0.122, 0.047, 0.282],
0.25: [0.333, 0.059, 0.427],
0.38: [0.533, 0.133, 0.416],
0.5: [0.729, 0.212, 0.333],
0.63: [0.89, 0.349, 0.2],
0.75: [0.976, 0.549, 0.039],
0.88: [0.976, 0.788, 0.196],
1: [0.988, 1, 0.643],
});
ColorMap.Bathymetry = ColorMap.makemap({
0: [0.157, 0.102, 0.173],
0.13: [0.233, 0.192, 0.353],
0.25: [0.251, 0.298, 0.545],
0.38: [0.247, 0.431, 0.592],
0.5: [0.282, 0.557, 0.62],
0.63: [0.333, 0.682, 0.639],
0.75: [0.471, 0.808, 0.639],
0.88: [0.733, 0.902, 0.675],
1: [0.992, 0.996, 0.8],
});
ColorMap.Viridis = ColorMap.makemap({
0: [0.267, 0.004, 0.329],
0.13: [0.278, 0.173, 0.478],
0.25: [0.231, 0.318, 0.545],
0.38: [0.173, 0.443, 0.557],
0.5: [0.129, 0.565, 0.553],
0.63: [0.153, 0.678, 0.506],
0.75: [0.361, 0.784, 0.388],
0.88: [0.667, 0.863, 0.196],
1: [0.992, 0.906, 0.145],
});
Color.normalize(ColorMap);
ColorMap.sample = function (t, map = this) {
if (t < 0) return map[0];
if (t > 1) return map[1];
var lastkey = 0;
for (var key of Object.keys(map).sort()) {
if (t < key) {
var b = map[key];
var a = map[lastkey];
var tt = (key - lastkey) * t;
return a.lerp(b, tt);
}
lastkey = key;
}
return map[1];
};
ColorMap.doc = {
sample: "Sample a given colormap at the given percentage (0 to 1).",
};
Color.maps = ColorMap
Color.utils = esc
return Color

View File

@@ -295,7 +295,7 @@ var Player = {
}, },
print_pawns() { print_pawns() {
[...this.pawns].reverse().forEach(x => console.log(x)) [...this.pawns].reverse().forEach(x => log.console(x))
}, },
create() { create() {

221
prosperon/draw2d.cm Normal file
View File

@@ -0,0 +1,221 @@
var math = use('math')
var color = use('color')
var draw = {}
draw[cell.DOC] = `
A collection of 2D drawing functions that create drawing command lists.
These are pure functions that return plain JavaScript objects representing
drawing operations. No rendering or actor communication happens here.
`
// 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
draw.clear = function() {
current_list.clear()
}
// Get commands from current list
draw.get_commands = function() {
return current_list.get()
}
// Helper to add a command
function add_command(type, data) {
var cmd = {cmd: type}
Object.assign(cmd, data)
current_list.push(cmd)
}
// Default geometry definitions
var ellipse_def = {
start: 0,
end: 1,
mode: 'fill',
thickness: 1,
}
var line_def = {
thickness: 1,
cap:"butt",
}
var rect_def = {
thickness:1,
radius: 0
}
var slice9_info = {
tile_top:true,
tile_bottom:true,
tile_left:true,
tile_right:true,
tile_center_x:true,
tile_center_right:true,
}
var image_info = {
tile_x: false,
tile_y: false,
flip_x: false,
flip_y: false,
mode: 'linear'
}
var circle_def = {
inner_radius:1, // percentage: 1 means filled circle
start:0,
end: 1,
}
// Drawing functions
draw.point = function(pos, size, opt = {}, material) {
add_command("draw_point", {
pos: pos,
size: size,
opt: opt,
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) {
var opt = def ? {...ellipse_def, ...def} : ellipse_def
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
add_command("draw_ellipse", {
pos: pos,
radii: radii,
opt: opt,
material: material
})
}
draw.line = function(points, def, material)
{
var opt = def ? {...line_def, ...def} : line_def
add_command("draw_line", {
points: points,
opt: opt,
material: material
})
}
draw.cross = function render_cross(pos, size, def, material) {
var a = [pos.add([0, size]), pos.add([0, -size])]
var b = [pos.add([size, 0]), pos.add([-size, 0])]
draw.line(a, def, material)
draw.line(b, def, material)
}
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, def, material) {
var dir = math.norm(end.sub(start))
var wing1 = [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(wing1, def, material)
draw.line(wing2, def, material)
}
draw.rectangle = function render_rectangle(rect, def, material) {
var opt = def ? {...rect_def, ...def} : rect_def
add_command("draw_rect", {
rect: rect,
opt: opt,
material: material
})
}
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, material) {
if (!image) throw Error('Need an image to render.')
add_command("draw_slice9", {
image: image,
rect: rect,
slice: slice,
info: info,
material: material
})
}
draw.image = function image(image, rect, rotation = 0, anchor = [0,0], shear = [0,0], info = {}, material) {
if (!rect) throw Error('Need rectangle to render image.')
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.')
info = Object.assign({}, image_info, info);
add_command("draw_image", {
image: image,
rect: rect,
rotation: rotation,
anchor: anchor,
shear: shear,
info: info,
material: material
})
}
draw.circle = function render_circle(pos, radius, def, material) {
draw.ellipse(pos, [radius,radius], def, material)
}
draw.text = function text(text, pos, font = 'fonts/c64.ttf', size = 8, color = color.white, wrap = 0) {
add_command("draw_text", {
text,
pos,
font,
size,
wrap,
material: {color}
})
}
return draw

View File

@@ -1,5 +1,4 @@
var Color = use('color') var color = use('color')
var os = use('os')
var graphics = use('graphics') var graphics = use('graphics')
var transform = use('transform') var transform = use('transform')
@@ -15,7 +14,7 @@ ex.garbage = function()
ex.update = function(dt) ex.update = function(dt)
{ {
for (var e of ex.emitters) for (var e of ex.emitters)
try { e.step(dt) } catch(e) { console.error(e) } try { e.step(dt) } catch(e) { log.error(e) }
} }
ex.step_hook = function(p) ex.step_hook = function(p)
@@ -104,7 +103,7 @@ ex.scale = 1
ex.grow_for = 0 ex.grow_for = 0
ex.spawn_timer = 0 ex.spawn_timer = 0
ex.pps = 0 ex.pps = 0
ex.color = Color.white ex.color = color.white
ex.draw = function() ex.draw = function()
{ {

View File

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -5,6 +5,7 @@ var sprite = use('sprite')
var geom = use('geometry') var geom = use('geometry')
var input = use('controller') var input = use('controller')
var config = use('config') var config = use('config')
var color = use('color')
var bunnyTex = graphics.texture("bunny") var bunnyTex = graphics.texture("bunny")
@@ -65,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}, undefined, 0, color.white, 0)
} }

View File

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 398 B

View File

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 337 B

View File

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View File

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 379 B

View File

@@ -1,16 +1,9 @@
/* main.js runs the demo with your prototype-based grid */ /* main.js runs the demo with your prototype-based grid */
var moth = use('moth', $_.delay)
var json = use('json') var json = use('json')
var draw2d = use('prosperon/draw2d')
var res = 160 var blob = use('blob')
var internal_res = 480
moth.initialize({ width:res, height:res, resolution_x:internal_res, resolution_y:internal_res, mode:'letterbox' });
var os = use('os');
var draw2d = use('draw2d');
var gfx = use('graphics');
/*──── import our pieces + systems ───────────────────────────────────*/ /*──── import our pieces + systems ───────────────────────────────────*/
var Grid = use('grid'); // your new ctor var Grid = use('grid'); // your new ctor
@@ -55,7 +48,7 @@ function updateTitle() {
break; break;
} }
prosperon.window.title = title log.console(title)
} }
// Initialize title // Initialize title
@@ -70,7 +63,7 @@ var opponentMousePos = null;
var opponentHoldingPiece = false; var opponentHoldingPiece = false;
var opponentSelectPos = null; var opponentSelectPos = null;
prosperon.on('mouse_button_down', function(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
@@ -96,9 +89,9 @@ prosperon.on('mouse_button_down', function(e) {
} else { } else {
selectPos = null; selectPos = null;
} }
}) }
prosperon.on('mouse_button_up', function(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
@@ -117,16 +110,16 @@ prosperon.on('mouse_button_up', function(e) {
} }
if (mover.tryMove(grid.at(selectPos)[0], c)) { if (mover.tryMove(grid.at(selectPos)[0], c)) {
console.log("Made move from", selectPos, "to", c); log.console("Made move from", selectPos, "to", c);
// Send move to opponent // Send move to opponent
console.log("Sending move to opponent:", opponent); log.console("Sending move to opponent:", opponent);
send(opponent, { send(opponent, {
type: 'move', type: 'move',
from: selectPos, from: selectPos,
to: c to: c
}); });
isMyTurn = false; // It's now opponent's turn isMyTurn = false; // It's now opponent's turn
console.log("Move sent, now opponent's turn"); log.console("Move sent, now opponent's turn");
selectPos = null; selectPos = null;
updateTitle(); updateTitle();
} }
@@ -139,9 +132,9 @@ prosperon.on('mouse_button_up', function(e) {
type: 'piece_drop' type: 'piece_drop'
}); });
} }
}) }
prosperon.on('mouse_motion', function(e) { function handleMouseMotion(e) {
var mx = e.pos.x; var mx = e.pos.x;
var my = e.pos.y; var my = e.pos.y;
@@ -162,7 +155,18 @@ prosperon.on('mouse_motion', function(e) {
selectPos: selectPos selectPos: selectPos
}); });
} }
}) }
function handleKeyDown(e) {
// S key - start server
if (e.scancode === 22 && gameState === 'waiting') { // S key
startServer();
}
// J key - join server
else if (e.scancode === 13 && gameState === 'waiting') { // J key
joinServer();
}
}
/*──── drawing helpers ───────────────────────────────────────────────*/ /*──── drawing helpers ───────────────────────────────────────────────*/
/* ── constants ─────────────────────────────────────────────────── */ /* ── constants ─────────────────────────────────────────────────── */
@@ -193,7 +197,8 @@ function drawBoard() {
draw2d.rectangle( draw2d.rectangle(
{ x: x*S, y: y*S, width: S, height: S }, { x: x*S, y: y*S, width: S, height: S },
{ thickness: 0, color: color } { thickness: 0 },
{ color: color }
); );
} }
} }
@@ -235,7 +240,7 @@ function drawPieces() {
var r = { x: piece.coord[0]*S, y: piece.coord[1]*S, var r = { x: piece.coord[0]*S, y: piece.coord[1]*S,
width:S, height:S }; width:S, height:S };
draw2d.image(piece.sprite, r, 0, [0,0], [0,0], {mode:"nearest"}); draw2d.image(piece.sprite, r);
}); });
// Draw the held piece at the mouse position if we're holding one // Draw the held piece at the mouse position if we're holding one
@@ -245,7 +250,7 @@ function drawPieces() {
var r = { x: hoverPos[0]*S, y: hoverPos[1]*S, var r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
width:S, height:S }; width:S, height:S };
draw2d.image(piece.sprite, r, 0, [0,0], [0,0], {mode:"nearest"}); draw2d.image(piece.sprite, r);
} }
} }
@@ -257,29 +262,25 @@ function drawPieces() {
width:S, height:S }; width:S, height:S };
// Draw with slight transparency to show it's the opponent's piece // Draw with slight transparency to show it's the opponent's piece
draw2d.image(opponentPiece.sprite, r, 0, [0,0], [0,0], {mode:"nearest", color: [1, 1, 1, 0.7]}); draw2d.image(opponentPiece.sprite, r);
} }
} }
} }
var graphics = use('graphics') function update(dt)
{
return {}
}
prosperon.on('draw', function() { function draw()
{
draw2d.clear()
drawBoard() drawBoard()
drawPieces() drawPieces()
draw2d.text("HELL", [100,100]) draw2d.text("HELL", {x: 100, y: 100}, 'fonts/c64.ttf', 16, [1,1,1,1])
}) return draw2d.get_commands()
}
prosperon.on('key_down', function(e) {
// S key - start server
if (e.scancode === 22 && gameState === 'waiting') { // S key
startServer();
}
// J key - join server
else if (e.scancode === 13 && gameState === 'waiting') { // J key
joinServer();
}
})
function startServer() { function startServer() {
gameState = 'server_waiting'; gameState = 'server_waiting';
@@ -289,11 +290,11 @@ function startServer() {
updateTitle(); updateTitle();
$_.portal(e => { $_.portal(e => {
console.log("Portal received contact message"); log.console("Portal received contact message");
// Reply with this actor to establish connection // Reply with this actor to establish connection
console.log (json.encode($_)) log.console (json.encode($_))
send(e, $_); send(e, $_);
console.log("Portal replied with server actor"); log.console("Portal replied with server actor");
}, 5678); }, 5678);
} }
@@ -302,10 +303,10 @@ function joinServer() {
updateTitle(); updateTitle();
function contact_fn(actor, reason) { function contact_fn(actor, reason) {
console.log("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason); log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
if (actor) { if (actor) {
opponent = actor; opponent = actor;
console.log("Connection established with server, sending join request"); log.console("Connection established with server, sending join request");
// Send a greet message with our actor object // Send a greet message with our actor object
send(opponent, { send(opponent, {
@@ -313,7 +314,7 @@ function joinServer() {
client_actor: $_ client_actor: $_
}); });
} else { } else {
console.log(`Failed to connect: ${json.encode(reason)}`); log.console(`Failed to connect: ${json.encode(reason)}`);
gameState = 'waiting'; gameState = 'waiting';
updateTitle(); updateTitle();
} }
@@ -325,51 +326,38 @@ function joinServer() {
}); });
} }
var os = use('os')
var actor = use('actor')
for (var i in actor) console.log(i)
// Set up IO actor subscription
var ioguy = {
__ACTORDATA__: {
id: actor.ioactor()
}
};
send(ioguy, {
type: "subscribe",
actor: $_
});
$_.receiver(e => { $_.receiver(e => {
if (e.type === 'game_start' || e.type === 'move' || e.type === 'greet') if (e.kind == 'update')
console.log("Receiver got message:", e.type, e); send(e, update(e.dt))
if (e.type === 'quit') os.exit() else if (e.kind == 'draw')
send(e, draw())
else if (e.type === 'game_start' || e.type === 'move' || e.type === 'greet')
log.console("Receiver got message:", e.type, e);
if (e.type === 'greet') { if (e.type === 'greet') {
console.log("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;
console.log("Stored client actor:", json.encode(opponent)); log.console("Stored client actor:", json.encode(opponent));
gameState = 'connected'; gameState = 'connected';
updateTitle(); updateTitle();
// Send game_start to the client // Send game_start to the client
console.log("Sending game_start to client"); log.console("Sending game_start to client");
send(opponent, { send(opponent, {
type: 'game_start', type: 'game_start',
your_color: 'black' your_color: 'black'
}); });
console.log("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') {
console.log("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') {
console.log("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);
if (fromCell.length) { if (fromCell.length) {
@@ -377,12 +365,12 @@ $_.receiver(e => {
if (mover.tryMove(piece, e.to)) { if (mover.tryMove(piece, e.to)) {
isMyTurn = true; // It's now our turn isMyTurn = true; // It's now our turn
updateTitle(); updateTitle();
console.log("Applied opponent move, now my turn"); log.console("Applied opponent move, now my turn");
} else { } else {
console.log("Failed to apply opponent move"); log.console("Failed to apply opponent move");
} }
} else { } else {
console.log("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
@@ -397,7 +385,13 @@ $_.receiver(e => {
// Opponent dropped their piece // Opponent dropped their piece
opponentHoldingPiece = false; opponentHoldingPiece = false;
opponentSelectPos = null; opponentSelectPos = null;
} else if (e.type === 'mouse_button_down') {
handleMouseButtonDown(e)
} else if (e.type === 'mouse_button_up') {
handleMouseButtonUp(e)
} else if (e.type === 'mouse_motion') {
handleMouseMotion(e)
} else if (e.type === 'key_down') {
handleKeyDown(e)
} }
})
prosperon.dispatch(e.type, e)
})

View File

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View File

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 403 B

View File

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 381 B

View File

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 313 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View File

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View File

@@ -2,6 +2,7 @@
var draw = use('draw2d') var draw = use('draw2d')
var input = use('controller') var input = use('controller')
var config = use('config') var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0] prosperon.camera.transform.pos = [0,0]
@@ -73,13 +74,13 @@ this.hud = function() {
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1]) draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw paddles // Draw paddles
draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, Color.white) draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, Color.white) draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
// Draw ball // Draw ball
draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, Color.white) draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, color.white)
// 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}, undefined, 0, color.white, 0)
} }

View File

@@ -4,6 +4,7 @@ var render = use('render')
var graphics = use('graphics') var graphics = use('graphics')
var input = use('input') var input = use('input')
var config = use('config') var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0] prosperon.camera.transform.pos = [0,0]
@@ -83,15 +84,15 @@ this.hud = function() {
// Draw snake // Draw snake
for (var i=0; i<snake.length; i++) { for (var i=0; i<snake.length; i++) {
var s = snake[i] var s = snake[i]
draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, Color.green) draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, color.green)
} }
// 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}, undefined, 0, color.white)
} }
} }

View File

@@ -23,23 +23,23 @@ var stats_loaded = false;
// Initialize Steam // Initialize Steam
function init_steam() { function init_steam() {
if (!steam) { if (!steam) {
console.log("Steam module not available"); log.console("Steam module not available");
return false; return false;
} }
console.log("Initializing Steam..."); log.console("Initializing Steam...");
steam_available = steam.steam_init(); steam_available = steam.steam_init();
if (steam_available) { if (steam_available) {
console.log("Steam initialized successfully"); log.console("Steam initialized successfully");
// Request current stats/achievements // Request current stats/achievements
if (steam.stats.stats_request()) { if (steam.stats.stats_request()) {
console.log("Stats requested"); log.console("Stats requested");
stats_loaded = true; stats_loaded = true;
} }
} else { } else {
console.log("Failed to initialize Steam"); log.console("Failed to initialize Steam");
} }
return steam_available; return steam_available;
@@ -59,13 +59,13 @@ function unlock_achievement(achievement_name) {
// Check if already unlocked // Check if already unlocked
var unlocked = steam.achievement.achievement_get(achievement_name); var unlocked = steam.achievement.achievement_get(achievement_name);
if (unlocked) { if (unlocked) {
console.log("Achievement already unlocked:", achievement_name); log.console("Achievement already unlocked:", achievement_name);
return true; return true;
} }
// Unlock it // Unlock it
if (steam.achievement.achievement_set(achievement_name)) { if (steam.achievement.achievement_set(achievement_name)) {
console.log("Achievement unlocked:", achievement_name); log.console("Achievement unlocked:", achievement_name);
// Store stats to make it permanent // Store stats to make it permanent
steam.stats.stats_store(); steam.stats.stats_store();
@@ -87,7 +87,7 @@ function update_stat(stat_name, value, is_float) {
} }
if (success) { if (success) {
console.log("Stat updated:", stat_name, "=", value); log.console("Stat updated:", stat_name, "=", value);
steam.stats.stats_store(); steam.stats.stats_store();
} }
@@ -115,7 +115,7 @@ function start_game() {
total_score = get_stat(STATS.TOTAL_SCORE, false); total_score = get_stat(STATS.TOTAL_SCORE, false);
current_score = 0; current_score = 0;
console.log("Starting game #" + (games_played + 1)); log.console("Starting game #" + (games_played + 1));
} }
function end_game(score) { function end_game(score) {
@@ -167,7 +167,7 @@ function load_from_cloud() {
function cleanup_steam() { function cleanup_steam() {
if (steam_available) { if (steam_available) {
steam.steam_shutdown(); steam.steam_shutdown();
console.log("Steam shut down"); log.console("Steam shut down");
} }
} }

View File

@@ -1,6 +1,7 @@
var draw = use('draw2d') var draw = use('draw2d')
var input = use('input') var input = use('input')
var config = use('config') var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0] prosperon.camera.transform.pos = [0,0]
@@ -248,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}, undefined, 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]
@@ -261,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}, undefined, 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}, undefined, 0, color.red)
} }
} }

View File

@@ -1,5 +1,5 @@
var geometry = this var geometry = this
geometry[prosperon.DOC] = ` geometry[cell.DOC] = `
A collection of geometry-related functions for circles, spheres, boxes, polygons, A collection of geometry-related functions for circles, spheres, boxes, polygons,
and rectangle utilities. Some functionality is implemented in C and exposed here. and rectangle utilities. Some functionality is implemented in C and exposed here.
` `
@@ -7,7 +7,7 @@ and rectangle utilities. Some functionality is implemented in C and exposed here
var math = use('math') var math = use('math')
geometry.box = {} geometry.box = {}
geometry.box[prosperon.DOC] = ` geometry.box[cell.DOC] = `
An object for box-related operations. Overridden later by a function definition, so An object for box-related operations. Overridden later by a function definition, so
its direct usage is overshadowed. Contains: its direct usage is overshadowed. Contains:
- points(ll, ur): Return an array of four 2D points for a box from ll (lower-left) to ur (upper-right). - points(ll, ur): Return an array of four 2D points for a box from ll (lower-left) to ur (upper-right).
@@ -16,7 +16,7 @@ its direct usage is overshadowed. Contains:
geometry.box.points = function (ll, ur) { geometry.box.points = function (ll, ur) {
return [ll, ll.add([ur.x - ll.x, 0]), ur, ll.add([0, ur.y - ll.y])] return [ll, ll.add([ur.x - ll.x, 0]), ur, ll.add([0, ur.y - ll.y])]
} }
geometry.box.points[prosperon.DOC] = ` geometry.box.points[cell.DOC] = `
:param ll: Lower-left coordinate as a 2D vector (x,y). :param ll: Lower-left coordinate as a 2D vector (x,y).
:param ur: Upper-right coordinate as a 2D vector (x,y). :param ur: Upper-right coordinate as a 2D vector (x,y).
:return: An array of four points forming the corners of the box in order [ll, lower-right, ur, upper-left]. :return: An array of four points forming the corners of the box in order [ll, lower-right, ur, upper-left].
@@ -24,14 +24,14 @@ Compute the four corners of a box given lower-left and upper-right corners.
` `
geometry.sphere = {} geometry.sphere = {}
geometry.sphere[prosperon.DOC] = ` geometry.sphere[cell.DOC] = `
Sphere-related geometry functions: Sphere-related geometry functions:
- volume(r): Return the volume of a sphere with radius r. - volume(r): Return the volume of a sphere with radius r.
- random(r, theta, phi): Return a random point on or inside a sphere. - random(r, theta, phi): Return a random point on or inside a sphere.
` `
geometry.circle = {} geometry.circle = {}
geometry.circle[prosperon.DOC] = ` geometry.circle[cell.DOC] = `
Circle-related geometry functions: Circle-related geometry functions:
- area(r): Return the area of a circle with radius r. - area(r): Return the area of a circle with radius r.
- random(r, theta): Return a random 2D point on a circle; uses sphere.random internally and extracts x,z. - random(r, theta): Return a random 2D point on a circle; uses sphere.random internally and extracts x,z.
@@ -40,7 +40,7 @@ Circle-related geometry functions:
geometry.sphere.volume = function (r) { geometry.sphere.volume = function (r) {
return (Math.pi * r * r * r * 4) / 3 return (Math.pi * r * r * r * 4) / 3
} }
geometry.sphere.volume[prosperon.DOC] = ` geometry.sphere.volume[cell.DOC] = `
:param r: The sphere radius. :param r: The sphere radius.
:return: The volume of the sphere, calculated as (4/3) * pi * r^3. :return: The volume of the sphere, calculated as (4/3) * pi * r^3.
` `
@@ -55,7 +55,7 @@ geometry.sphere.random = function (r, theta = [0, 1], phi = [-0.5, 0.5]) {
var pa = Math.turn2rad(Math.random_range(phi[0], phi[1])) var pa = Math.turn2rad(Math.random_range(phi[0], phi[1]))
return [ra * Math.sin(ta) * Math.cos(pa), ra * Math.sin(ta) * Math.sin(pa), ra * Math.cos(ta)] return [ra * Math.sin(ta) * Math.cos(pa), ra * Math.sin(ta) * Math.sin(pa), ra * Math.cos(ta)]
} }
geometry.sphere.random[prosperon.DOC] = ` geometry.sphere.random[cell.DOC] = `
:param r: A single number (radius) or a 2-element array [minRadius, maxRadius]. :param r: A single number (radius) or a 2-element array [minRadius, maxRadius].
:param theta: A single number or 2-element array defining the range in turns for the theta angle, default [0,1]. :param theta: A single number or 2-element array defining the range in turns for the theta angle, default [0,1].
:param phi: A single number or 2-element array defining the range in turns for the phi angle, default [-0.5,0.5]. :param phi: A single number or 2-element array defining the range in turns for the phi angle, default [-0.5,0.5].
@@ -66,7 +66,7 @@ Generate a random point inside a sphere of variable radius, distributing angles
geometry.circle.area = function (r) { geometry.circle.area = function (r) {
return Math.pi * r * r return Math.pi * r * r
} }
geometry.circle.area[prosperon.DOC] = ` geometry.circle.area[cell.DOC] = `
:param r: Radius of the circle. :param r: Radius of the circle.
:return: The area, pi * r^2. :return: The area, pi * r^2.
` `
@@ -74,7 +74,7 @@ geometry.circle.area[prosperon.DOC] = `
geometry.circle.random = function (r, theta) { geometry.circle.random = function (r, theta) {
return geometry.sphere.random(r, theta).xz return geometry.sphere.random(r, theta).xz
} }
geometry.circle.random[prosperon.DOC] = ` geometry.circle.random[cell.DOC] = `
:param r: A radius or [minRadius, maxRadius]. :param r: A radius or [minRadius, maxRadius].
:param theta: Angle range in turns (single number or [min,max]). :param theta: Angle range in turns (single number or [min,max]).
:return: A 2D point (x,z) in the circle, using the sphere random generator and ignoring y. :return: A 2D point (x,z) in the circle, using the sphere random generator and ignoring y.
@@ -91,7 +91,7 @@ geometry.box = function (w, h) {
] ]
return points return points
} }
geometry.box[prosperon.DOC] = ` geometry.box[cell.DOC] = `
:param w: The width of the box. :param w: The width of the box.
:param h: The height of the box. :param h: The height of the box.
:return: An array of four 2D points representing the corners of a rectangle centered at [0,0]. :return: An array of four 2D points representing the corners of a rectangle centered at [0,0].
@@ -101,7 +101,7 @@ Construct a box centered at the origin with the given width and height. This ove
geometry.ngon = function (radius, n) { geometry.ngon = function (radius, n) {
return geometry.arc(radius, 360, n) return geometry.arc(radius, 360, n)
} }
geometry.ngon[prosperon.DOC] = ` geometry.ngon[cell.DOC] = `
:param radius: The radius of the n-gon from center to each vertex. :param radius: The radius of the n-gon from center to each vertex.
:param n: Number of sides/vertices. :param n: Number of sides/vertices.
:return: An array of 2D points forming a regular n-gon. :return: An array of 2D points forming a regular n-gon.
@@ -118,7 +118,7 @@ geometry.arc = function (radius, angle, n, start = 0) {
for (var i = 0; i < n; i++) points.push(math.rotate([radius, 0], start + arclen * i)) for (var i = 0; i < n; i++) points.push(math.rotate([radius, 0], start + arclen * i))
return points return points
} }
geometry.arc[prosperon.DOC] = ` geometry.arc[cell.DOC] = `
:param radius: The distance from center to the arc points. :param radius: The distance from center to the arc points.
:param angle: The total angle (in degrees) over which points are generated, capped at 360. :param angle: The total angle (in degrees) over which points are generated, capped at 360.
:param n: Number of segments (if <=1, empty array is returned). :param n: Number of segments (if <=1, empty array is returned).
@@ -131,7 +131,7 @@ geometry.circle.points = function (radius, n) {
if (n <= 1) return [] if (n <= 1) return []
return geometry.arc(radius, 360, n) return geometry.arc(radius, 360, n)
} }
geometry.circle.points[prosperon.DOC] = ` geometry.circle.points[cell.DOC] = `
:param radius: The circle's radius. :param radius: The circle's radius.
:param n: Number of points around the circle. :param n: Number of points around the circle.
:return: An array of 2D points equally spaced around a full 360-degree circle. :return: An array of 2D points equally spaced around a full 360-degree circle.
@@ -141,7 +141,7 @@ Shortcut for geometry.arc(radius, 360, n).
geometry.corners2points = function (ll, ur) { geometry.corners2points = function (ll, ur) {
return [ll, ll.add([ur.x, 0]), ur, ll.add([0, ur.y])] return [ll, ll.add([ur.x, 0]), ur, ll.add([0, ur.y])]
} }
geometry.corners2points[prosperon.DOC] = ` geometry.corners2points[cell.DOC] = `
:param ll: Lower-left 2D coordinate. :param ll: Lower-left 2D coordinate.
:param ur: Upper-right 2D coordinate (relative offset in x,y). :param ur: Upper-right 2D coordinate (relative offset in x,y).
:return: A four-point array of corners [ll, lower-right, upper-right, upper-left]. :return: A four-point array of corners [ll, lower-right, upper-right, upper-left].
@@ -158,7 +158,7 @@ geometry.sortpointsccw = function (points) {
}) })
return ccw.map(function (x) { return x.add(cm) }) return ccw.map(function (x) { return x.add(cm) })
} }
geometry.sortpointsccw[prosperon.DOC] = ` geometry.sortpointsccw[cell.DOC] = `
:param points: An array of 2D points. :param points: An array of 2D points.
:return: A new array of the same points, sorted counterclockwise around their centroid. :return: A new array of the same points, sorted counterclockwise around their centroid.
Sort an array of points in CCW order based on their angles from the centroid. Sort an array of points in CCW order based on their angles from the centroid.
@@ -185,61 +185,61 @@ geometry.points2cm = function(points) {
}) })
return [x / n, y / n] return [x / n, y / n]
} }
geometry.points2cm[prosperon.DOC] = ` geometry.points2cm[cell.DOC] = `
:param points: An array of 2D points. :param points: An array of 2D points.
:return: The centroid (average x,y) of the given points. :return: The centroid (average x,y) of the given points.
` `
geometry.rect_intersection[prosperon.DOC] = ` geometry.rect_intersection[cell.DOC] = `
:param a: The first rectangle as {x, y, w, h}. :param a: The first rectangle as {x, y, w, h}.
:param b: The second rectangle as {x, y, w, h}. :param b: The second rectangle as {x, y, w, h}.
:return: A rectangle that is the intersection of the two. May have zero width/height if no overlap. :return: A rectangle that is the intersection of the two. May have zero width/height if no overlap.
Return the intersection of two rectangles. The result may be empty if no intersection. Return the intersection of two rectangles. The result may be empty if no intersection.
` `
geometry.rect_intersects[prosperon.DOC] = ` geometry.rect_intersects[cell.DOC] = `
:param a: Rectangle {x,y,w,h}. :param a: Rectangle {x,y,w,h}.
:param b: Rectangle {x,y,w,h}. :param b: Rectangle {x,y,w,h}.
:return: A boolean indicating whether the two rectangles overlap. :return: A boolean indicating whether the two rectangles overlap.
` `
geometry.rect_expand[prosperon.DOC] = ` geometry.rect_expand[cell.DOC] = `
:param a: Rectangle {x,y,w,h}. :param a: Rectangle {x,y,w,h}.
:param b: Rectangle {x,y,w,h}. :param b: Rectangle {x,y,w,h}.
:return: A new rectangle that covers the bounds of both input rectangles. :return: A new rectangle that covers the bounds of both input rectangles.
Merge or combine two rectangles, returning their bounding rectangle. Merge or combine two rectangles, returning their bounding rectangle.
` `
geometry.rect_inside[prosperon.DOC] = ` geometry.rect_inside[cell.DOC] = `
:param inner: A rectangle to test. :param inner: A rectangle to test.
:param outer: A rectangle that may contain 'inner'. :param outer: A rectangle that may contain 'inner'.
:return: True if 'inner' is completely inside 'outer', otherwise false. :return: True if 'inner' is completely inside 'outer', otherwise false.
` `
geometry.rect_random[prosperon.DOC] = ` geometry.rect_random[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}. :param rect: A rectangle {x,y,w,h}.
:return: A random point within the rectangle (uniform distribution). :return: A random point within the rectangle (uniform distribution).
` `
geometry.cwh2rect[prosperon.DOC] = ` geometry.cwh2rect[cell.DOC] = `
:param center: A 2D point [cx, cy]. :param center: A 2D point [cx, cy].
:param wh: A 2D size [width, height]. :param wh: A 2D size [width, height].
:return: A rectangle {x, y, w, h} with x,y set to center and w,h set to the given size. :return: A rectangle {x, y, w, h} with x,y set to center and w,h set to the given size.
Helper: convert a center point and width/height vector to a rect object. Helper: convert a center point and width/height vector to a rect object.
` `
geometry.rect_point_inside[prosperon.DOC] = ` geometry.rect_point_inside[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}. :param rect: A rectangle {x,y,w,h}.
:param point: A 2D point [px, py]. :param point: A 2D point [px, py].
:return: True if the point lies inside the rectangle, otherwise false. :return: True if the point lies inside the rectangle, otherwise false.
` `
geometry.rect_pos[prosperon.DOC] = ` geometry.rect_pos[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}. :param rect: A rectangle {x,y,w,h}.
:return: A 2D vector [x,y] giving the rectangle's position. :return: A 2D vector [x,y] giving the rectangle's position.
` `
geometry.rect_move[prosperon.DOC] = ` geometry.rect_move[cell.DOC] = `
:param rect: A rectangle {x,y,w,h}. :param rect: A rectangle {x,y,w,h}.
:param offset: A 2D vector to add to the rectangle's position. :param offset: A 2D vector to add to the rectangle's position.
:return: A new rectangle with updated x,y offset. :return: A new rectangle with updated x,y offset.

View File

@@ -1,59 +1,105 @@
var graphics = this var graphics = this
graphics[prosperon.DOC] = ` graphics[cell.DOC] = `
Provides functionality for loading and managing images, fonts, textures, and sprite meshes. Provides functionality for loading and managing images, fonts, textures, and sprite meshes.
Includes both JavaScript and C-implemented routines for creating geometry buffers, performing Includes both JavaScript and C-implemented routines for creating geometry buffers, performing
rectangle packing, etc. rectangle packing, etc.
` `
var renderer_actor = arg[0]
var renderer_id = arg[1]
var io = use('io') var io = use('io')
var os = use('os') var time = use('time')
var res = use('resources') var res = use('resources')
var render = use('render') var json = use('json')
var GPU = Symbol() var GPU = Symbol()
var CPU = Symbol() var CPU = Symbol()
var LASTUSE = Symbol() var LASTUSE = Symbol()
var LOADING = Symbol()
var cache = new Map() var cache = new Map()
// When creating an image, do an Object.create(graphics.Image) // Image constructor function
graphics.Image = { graphics.Image = function(surfaceData) {
get gpu() { // Initialize private properties
this[LASTUSE] = os.now(); this[CPU] = surfaceData || undefined;
if (!this[GPU]) { this[GPU] = undefined;
this[GPU] = render.load_texture(this[CPU]); this[LOADING] = false;
decorate_rect_px(this); this[LASTUSE] = time.number();
} this.rect = {x:0, y:0, width:1, height:1};
}
return this[GPU]
},
get texture() { return this.gpu },
get cpu() { // Define getters and methods on the prototype
this[LASTUSE] = os.now(); Object.defineProperties(graphics.Image.prototype, {
if (!this[CPU]) this[CPU] = render.read_texture(this[GPU]) gpu: {
return this[CPU] get: function() {
this[LASTUSE] = time.number();
if (!this[GPU] && !this[LOADING]) {
this[LOADING] = true;
var self = this;
// Send message to load texture
send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "loadTexture",
data: this[CPU]
}, function(response) {
if (response.error) {
log.error("Failed to load texture:")
log.error(response.error)
self[LOADING] = false;
} else {
self[GPU] = response;
decorate_rect_px(self);
self[LOADING] = false;
}
});
}
return this[GPU]
}
}, },
get surface() { return this.cpu }, texture: {
get: function() { return this.gpu }
get width() { },
return this[GPU].width
cpu: {
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]
}
}, },
get height() { surface: {
return this[GPU].height get: function() { return this.cpu }
}, },
unload_gpu() { width: {
this[GPU] = undefined get: function() {
return this[CPU].width
}
}, },
unload_cpu() { height: {
this[CPU] = undefined get: function() {
}, return this[CPU].height
}
}
});
// Add methods to prototype
graphics.Image.prototype.unload_gpu = function() {
this[GPU] = undefined
}
graphics.Image.prototype.unload_cpu = function() {
this[CPU] = undefined
} }
function calc_image_size(img) { function calc_image_size(img) {
@@ -79,17 +125,7 @@ function decorate_rect_px(img) {
function make_handle(obj) function make_handle(obj)
{ {
var image = Object.create(graphics.Image); return new graphics.Image(obj);
if (obj.surface) {
im
}
return Object.assign(Object.create(graphics.Image), {
rect:{x:0,y:0,width:1,height:1},
[CPU]:obj,
[GPU]:undefined,
[LASTUSE]:os.now()
})
} }
function wrapSurface(surf, maybeRect){ function wrapSurface(surf, maybeRect){
@@ -121,11 +157,14 @@ function decode_image(bytes, ext)
function create_image(path){ function create_image(path){
try{ try{
const bytes = io.slurpbytes(path); const bytes = io.slurpbytes(path);
let raw = decode_image(bytes, path.ext());
let raw = decode_image(bytes, path.ext());
/* ── Case A: static image ─────────────────────────────────── */ /* ── Case A: static image ─────────────────────────────────── */
if(raw.surface) if(raw.surface) {
return make_handle(raw.surface); var gg = new graphics.Image(raw.surface)
return gg
}
/* ── Case B: GIF helpers returned array [surf, …] ─────────── */ /* ── Case B: GIF helpers returned array [surf, …] ─────────── */
if(Array.isArray(raw)) if(Array.isArray(raw))
@@ -149,7 +188,7 @@ function create_image(path){
throw new Error('Unsupported image structure from decoder'); throw new Error('Unsupported image structure from decoder');
}catch(e){ }catch(e){
console.error(`Error loading image ${path}: ${e.message}`); log.error(`Error loading image ${path}: ${e.message}`);
throw e; throw e;
} }
} }
@@ -158,7 +197,7 @@ var image = {}
image.dimensions = function() { image.dimensions = function() {
return [this.texture.width, this.texture.height].scale([this.rect[2], this.rect[3]]) return [this.texture.width, this.texture.height].scale([this.rect[2], this.rect[3]])
} }
image.dimensions[prosperon.DOC] = ` image.dimensions[cell.DOC] = `
:return: A 2D array [width, height] that is the scaled size of this image (texture size * rect size). :return: A 2D array [width, height] that is the scaled size of this image (texture size * rect size).
` `
@@ -179,7 +218,7 @@ function pack_into_sheet(images) {
graphics.is_image = function(obj) { graphics.is_image = function(obj) {
if (obj.texture && obj.rect) return true if (obj.texture && obj.rect) return true
} }
graphics.is_image[prosperon.DOC] = ` graphics.is_image[cell.DOC] = `
:param obj: An object to check. :param obj: An object to check.
:return: True if 'obj' has a .texture and a .rect property, indicating it's an image object. :return: True if 'obj' has a .texture and a .rect property, indicating it's an image object.
` `
@@ -188,19 +227,17 @@ graphics.texture_from_data = function(data)
{ {
if (!(data instanceof ArrayBuffer)) return undefined if (!(data instanceof ArrayBuffer)) return undefined
var img = { var image = graphics.make_texture(data);
surface: graphics.make_texture(data) var img = make_handle(image)
}
render.load_texture(img) img.gpu;
decorate_rect_px(img)
return img return img;
} }
graphics.from_surface = function(id, surf) graphics.from_surface = function(id, surf)
{ {
return make_handle(surf) return make_handle(surf)
var img = { surface: surf }
} }
graphics.from = function(id, data) graphics.from = function(id, data)
@@ -213,7 +250,7 @@ graphics.from = function(id, data)
} }
graphics.texture = function texture(path) { graphics.texture = function texture(path) {
if (path.__proto__ === 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')
@@ -222,13 +259,14 @@ graphics.texture = function texture(path) {
if (cache.has(id)) return cache.get(id) if (cache.has(id)) return cache.get(id)
var ipath = res.find_image(id) var ipath = res.find_image(id)
if (!ipath) throw new Error(`unknown image ${id}`) if (!ipath)
throw new Error(`unknown image ${id}`)
var image = create_image(ipath) var image = create_image(ipath)
cache.set(id, image) cache.set(id, image)
return image return image
} }
graphics.texture[prosperon.DOC] = ` graphics.texture[cell.DOC] = `
:param path: A string path to an image file or an already-loaded image object. :param path: A string path to an image file or an already-loaded image object.
:return: An image object with {surface, texture, frames?, etc.} depending on the format. :return: An image object with {surface, texture, frames?, etc.} depending on the format.
Load or retrieve a cached image, converting it into a GPU texture. If 'path' is already an object, its returned directly. Load or retrieve a cached image, converting it into a GPU texture. If 'path' is already an object, its returned directly.
@@ -242,7 +280,7 @@ graphics.texture.total_size = function() {
// Not yet implemented, presumably sum of (texture.width * texture.height * 4) for images in RAM // Not yet implemented, presumably sum of (texture.width * texture.height * 4) for images in RAM
return size return size
} }
graphics.texture.total_size[prosperon.DOC] = ` graphics.texture.total_size[cell.DOC] = `
:return: The total estimated memory size of all cached textures in RAM, in bytes. (Not yet implemented.) :return: The total estimated memory size of all cached textures in RAM, in bytes. (Not yet implemented.)
` `
@@ -251,23 +289,23 @@ graphics.texture.total_vram = function() {
// Not yet implemented, presumably sum of GPU memory usage // Not yet implemented, presumably sum of GPU memory usage
return vram return vram
} }
graphics.texture.total_vram[prosperon.DOC] = ` graphics.texture.total_vram[cell.DOC] = `
:return: The total estimated GPU memory usage of all cached textures, in bytes. (Not yet implemented.) :return: The total estimated GPU memory usage of all cached textures, in bytes. (Not yet implemented.)
` `
graphics.tex_hotreload = function tex_hotreload(file) { graphics.tex_hotreload = function tex_hotreload(file) {
console.log(`hot reloading ${file}`) log.console(`hot reloading ${file}`)
if (!(file in graphics.texture.cache)) return if (!(file in graphics.texture.cache)) return
console.log('really doing it') log.console('really doing it')
var img = create_image(file) var img = create_image(file)
var oldimg = graphics.texture.cache[file] var oldimg = graphics.texture.cache[file]
console.log(`new image:${json.encode(img)}`) log.console(`new image:${json.encode(img)}`)
console.log(`old image: ${json.encode(oldimg)}`) log.console(`old image: ${json.encode(oldimg)}`)
merge_objects(oldimg, img, ['surface', 'texture', 'loop', 'time']) merge_objects(oldimg, img, ['surface', 'texture', 'loop', 'time'])
} }
graphics.tex_hotreload[prosperon.DOC] = ` graphics.tex_hotreload[cell.DOC] = `
:param file: The file path that was changed on disk. :param file: The file path that was changed on disk.
:return: None :return: None
Reload the image for the given file, updating the cached copy in memory and GPU. Reload the image for the given file, updating the cached copy in memory and GPU.
@@ -307,16 +345,26 @@ graphics.get_font = function get_font(path, size) {
var data = io.slurpbytes(fullpath) var data = io.slurpbytes(fullpath)
var font = graphics.make_font(data,size) var font = graphics.make_font(data,size)
font.texture = render.load_texture(font.surface)
console.log('loaded font texture') // Load font texture via renderer actor (async)
console.log(json.encode(font.texture)) send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "loadTexture",
data: font.surface
}, function(response) {
if (response.error) {
log.error("Failed to load font texture:", response.error);
} else {
font.texture = response;
}
});
fontcache[fontstr] = font fontcache[fontstr] = font
return font return font
} }
graphics.get_font[prosperon.DOC] = ` graphics.get_font[cell.DOC] = `
:param path: A string path to a font file, optionally with ".size" appended. :param path: A string path to a font file, optionally with ".size" appended.
:param size: Pixel size of the font, if not included in 'path'. :param size: Pixel size of the font, if not included in 'path'.
:return: A font object with .surface and .texture for rendering text. :return: A font object with .surface and .texture for rendering text.
@@ -334,31 +382,14 @@ graphics.queue_sprite_mesh = function(queue) {
} }
return [mesh.pos, mesh.uv, mesh.color, mesh.indices] return [mesh.pos, mesh.uv, mesh.color, mesh.indices]
} }
graphics.queue_sprite_mesh[prosperon.DOC] = ` graphics.queue_sprite_mesh[cell.DOC] = `
:param queue: An array of draw commands, some of which are {type:'sprite'} objects. :param queue: An array of draw commands, some of which are {type:'sprite'} objects.
:return: An array of references to GPU buffers [pos,uv,color,indices]. :return: An array of references to GPU buffers [pos,uv,color,indices].
Builds a single geometry mesh for all sprite-type commands in the queue, storing first_index/num_indices Builds a single geometry mesh for all sprite-type commands in the queue, storing first_index/num_indices
so they can be rendered in one draw call. so they can be rendered in one draw call.
` `
graphics.make_sprite_mesh[prosperon.DOC] = ` graphics.make_text_buffer[cell.DOC] = `
:param sprites: An array of sprite objects, each containing .rect (or transform), .src (UV region), .color, etc.
:param oldMesh (optional): An existing mesh object to reuse/resize if possible.
:return: A GPU mesh object with pos, uv, color, and indices buffers for all sprites.
Given an array of sprites, build a single geometry mesh for rendering them.
`
graphics.make_sprite_queue[prosperon.DOC] = `
:param sprites: An array of sprite objects.
:param camera: (unused in the C code example) Typically a camera or transform for sorting?
:param pipeline: A pipeline object for rendering.
:param sort: An integer or boolean for whether to sort sprites; if truthy, sorts by layer & texture.
:return: An array of pipeline commands: geometry with mesh references, grouped by image.
Given an array of sprites, optionally sort them, then build a queue of pipeline commands.
Each group with a shared image becomes one command.
`
graphics.make_text_buffer[prosperon.DOC] = `
:param text: The string to render. :param text: The string to render.
:param rect: A rectangle specifying position and possibly wrapping. :param rect: A rectangle specifying position and possibly wrapping.
:param angle: Rotation angle (unused or optional). :param angle: Rotation angle (unused or optional).
@@ -369,7 +400,7 @@ graphics.make_text_buffer[prosperon.DOC] = `
Generate a GPU buffer mesh of text quads for rendering with a font, etc. Generate a GPU buffer mesh of text quads for rendering with a font, etc.
` `
graphics.rectpack[prosperon.DOC] = ` graphics.rectpack[cell.DOC] = `
:param width: The width of the area to pack into. :param width: The width of the area to pack into.
:param height: The height of the area to pack into. :param height: The height of the area to pack into.
:param sizes: An array of [w,h] pairs for the rectangles to pack. :param sizes: An array of [w,h] pairs for the rectangles to pack.
@@ -377,50 +408,39 @@ graphics.rectpack[prosperon.DOC] = `
Perform a rectangle packing using the stbrp library. Return positions for each rect. Perform a rectangle packing using the stbrp library. Return positions for each rect.
` `
graphics.make_texture[prosperon.DOC] = ` graphics.make_texture[cell.DOC] = `
:param data: Raw image bytes (PNG, JPG, etc.) as an ArrayBuffer. :param data: Raw image bytes (PNG, JPG, etc.) as an ArrayBuffer.
:return: An SDL_Surface object representing the decoded image in RAM, for use with GPU or software rendering. :return: An SDL_Surface object representing the decoded image in RAM, for use with GPU or software rendering.
Convert raw image bytes into an SDL_Surface object. Convert raw image bytes into an SDL_Surface object.
` `
graphics.make_gif[prosperon.DOC] = ` graphics.make_gif[cell.DOC] = `
:param data: An ArrayBuffer containing GIF data. :param data: An ArrayBuffer containing GIF data.
:return: An object with frames[], each frame having its own .surface. Some also have a .texture for GPU use. :return: An object with frames[], each frame having its own .surface. Some also have a .texture for GPU use.
Load a GIF, returning its frames. If it's a single-frame GIF, the result may have .surface only. Load a GIF, returning its frames. If it's a single-frame GIF, the result may have .surface only.
` `
graphics.make_aseprite[prosperon.DOC] = ` graphics.make_aseprite[cell.DOC] = `
:param data: An ArrayBuffer containing Aseprite (ASE) file data. :param data: An ArrayBuffer containing Aseprite (ASE) file data.
:return: An object containing frames or animations, each with .surface. May also have top-level .surface for a single-layer case. :return: An object containing frames or animations, each with .surface. May also have top-level .surface for a single-layer case.
Load an Aseprite/ASE file from an array of bytes, returning frames or animations. Load an Aseprite/ASE file from an array of bytes, returning frames or animations.
` `
graphics.cull_sprites[prosperon.DOC] = ` graphics.cull_sprites[cell.DOC] = `
:param sprites: An array of sprite objects (each has rect or transform). :param sprites: An array of sprite objects (each has rect or transform).
:param camera: A camera or bounding rectangle defining the view area. :param camera: A camera or bounding rectangle defining the view area.
:return: A new array of sprites that are visible in the camera's view. :return: A new array of sprites that are visible in the camera's view.
Filter an array of sprites to only those visible in the provided cameras view. Filter an array of sprites to only those visible in the provided cameras view.
` `
graphics.make_surface[prosperon.DOC] = ` graphics.make_font[cell.DOC] = `
:param dimensions: The size object {width, height}, or an array [w,h].
:return: A blank RGBA surface with the given dimensions, typically for software rendering or icons.
Create a blank surface in RAM.
`
graphics.make_cursor[prosperon.DOC] = `
:param opts: An object with {surface, hotx, hoty} or similar.
:return: An SDL_Cursor object referencing the given surface for a custom mouse cursor.
`
graphics.make_font[prosperon.DOC] = `
:param data: TTF/OTF file data as an ArrayBuffer. :param data: TTF/OTF file data as an ArrayBuffer.
:param size: Pixel size for rendering glyphs. :param size: Pixel size for rendering glyphs.
:return: A font object with surface, texture, and glyph data, for text rendering with make_text_buffer. :return: A font object with surface, texture, and glyph data, for text rendering with make_text_buffer.
Load a font from TTF/OTF data at the given size. Load a font from TTF/OTF data at the given size.
` `
graphics.make_line_prim[prosperon.DOC] = ` graphics.make_line_prim[cell.DOC] = `
:param points: An array of [x,y] points forming the line. :param points: An array of [x,y] points forming the line.
:param thickness: The thickness (width) of the polyline. :param thickness: The thickness (width) of the polyline.
:param startCap: (Unused) Possibly the type of cap for the start. :param startCap: (Unused) Possibly the type of cap for the start.

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 867 B

After

Width:  |  Height:  |  Size: 867 B

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Some files were not shown because too many files have changed in this diff Show More