202 Commits

Author SHA1 Message Date
John Alanbrook
816dd664c2 initial discord attempt 2025-07-17 20:46:42 -05:00
John Alanbrook
881407a64f massively expand steam api 2025-07-17 20:28:47 -05:00
John Alanbrook
e677832b12 fix dmon leak; fix underlings not stopping when overling crashes 2025-07-17 16:32:22 -05:00
John Alanbrook
13c1e7560a empty text works 2025-07-16 20:18:50 -05:00
John Alanbrook
2e275adcd2 bug fix: graphics now correctly returns frames of single anims 2025-07-16 18:11:38 -05:00
John Alanbrook
2607604a0b play gifs with strings 2025-07-16 17:45:12 -05:00
John Alanbrook
d1d9a296a8 separate clay layout and clay input 2025-07-16 14:51:19 -05:00
John Alanbrook
7edbb85e4e clay id handling 2025-07-16 14:37:38 -05:00
John Alanbrook
874252db87 fix text menu render 2025-07-16 05:03:40 -05:00
John Alanbrook
13dd685f65 render layers 2025-07-15 20:33:55 -05:00
John Alanbrook
4b817b8d1b text drawing 2025-07-15 16:22:11 -05:00
John Alanbrook
09b78781e6 animations 2025-07-15 15:06:06 -05:00
John Alanbrook
19ce1008b1 emitter 2025-07-14 09:52:25 -05:00
John Alanbrook
d1c7ff768d prosperon module 2025-07-14 03:14:06 -05:00
John Alanbrook
0d97b47728 support for gamepad events 2025-07-12 22:38:18 -05:00
John Alanbrook
c87f85cf6c Update files for cross compilation fixes; add dockerfiles for windows/linux/emscripten builds; add commands to makefile to build via dockerfiles
Some checks failed
Build and Deploy / build-macos (push) Failing after 29s
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-07-12 20:39:25 -05:00
John Alanbrook
f0afdfc7d9 add base64 and base64url encoder/decoders 2025-07-12 10:31:54 -05:00
John Alanbrook
ac9f40fd26 add set window size 2025-07-11 21:22:10 -05:00
John Alanbrook
39152c1eb2 fix base32 conversion/deconversion 2025-07-11 19:38:49 -05:00
John Alanbrook
6ff50cb521 add combo box to imgui 2025-07-11 14:11:34 -05:00
John Alanbrook
2b7b3985d5 tilemap render 2025-07-10 18:10:55 -05:00
John Alanbrook
a9b59750e3 fix mouse pos 2025-07-09 22:55:41 -05:00
John Alanbrook
525263a8a6 add imgui 2025-07-08 01:43:52 -05:00
John Alanbrook
310a0db99e fix draw 2025-07-07 18:51:18 -05:00
John Alanbrook
6e66bf59f6 input reports mod keys now 2025-07-07 15:26:45 -05:00
John Alanbrook
71c0056df4 layout uses x,y 2025-07-06 20:35:15 -05:00
John Alanbrook
d4f0059419 add benchmark and cell doc 2025-07-05 10:25:27 -05:00
John Alanbrook
d52d50fe61 fixes for gameplay 2025-07-05 10:24:09 -05:00
John Alanbrook
1bc34bb99c fix some rendering bug 2025-06-24 08:33:29 -05:00
John Alanbrook
8ac78e0be6 add layout 2025-06-23 22:58:25 -05:00
John Alanbrook
dca3ede464 add function disassembler 2025-06-23 21:28:15 -05:00
John Alanbrook
7b622d9788 initial attempt at adding IC 2025-06-23 17:20:39 -05:00
John Alanbrook
42087910ab remove proxy and many exotic methods 2025-06-23 15:32:05 -05:00
John Alanbrook
c581935fd8 add detail to cell.md 2025-06-23 10:21:33 -05:00
John Alanbrook
632b038561 remove last two master updates and remove proxy 2025-06-23 10:21:07 -05:00
John Alanbrook
6eb33b8e48 remove unneeded object properties and if 0 code 2025-06-19 08:46:40 -05:00
John Alanbrook
3d5f345236 all compilation is strict 2025-06-18 16:37:55 -05:00
John Alanbrook
4689ea1167 remove module eval branches 2025-06-18 14:10:51 -05:00
John Alanbrook
15d85096a2 remove global symbols 2025-06-18 14:03:12 -05:00
John Alanbrook
f2c2ecf692 remove all private stuff 2025-06-18 13:59:47 -05:00
John Alanbrook
458215f838 remove arguments object 2025-06-18 13:24:22 -05:00
John Alanbrook
7f002e306d remove reflect 2025-06-18 08:46:42 -05:00
John Alanbrook
91a3fef065 finish removing big int stuff; fixes memory leak 2025-06-17 21:36:06 -05:00
John Alanbrook
843b4bd8a8 remove usage of map 2025-06-17 21:16:35 -05:00
John Alanbrook
41fdf49df5 remove bigint 2025-06-17 15:02:40 -05:00
John Alanbrook
c9adbed3ff remove undefined; only use null now 2025-06-17 14:32:27 -05:00
John Alanbrook
3459d85a82 binary logic only works on ints; returns null otherwise 2025-06-17 12:54:20 -05:00
John Alanbrook
9ecdaae7a7 simpler addition rule 2025-06-16 20:40:41 -05:00
John Alanbrook
43b55b29f3 comparing anything but two numbers or two strings is an error 2025-06-16 18:45:57 -05:00
John Alanbrook
a88cee7fae Remove weak-everything. remove throwing on type differences in equality; remove using 'def' as some variable names 2025-06-16 17:02:24 -05:00
John Alanbrook
8b3f5476a9 remove module checks 2025-06-16 15:56:04 -05:00
John Alanbrook
233f59e04a clean up unused functions 2025-06-16 15:49:54 -05:00
John Alanbrook
7b16259c00 Merge branch 'js-rm-eq' into js-rm-modules 2025-06-16 14:20:23 -05:00
John Alanbrook
43baa23dfe Merge remote-tracking branch 'origin/js-rm-htmldda' into js-rm-modules 2025-06-16 14:18:40 -05:00
John Alanbrook
a9ebea9f26 Merge remote-tracking branch 'origin/js-def' into js-rm-modules 2025-06-16 14:16:01 -05:00
John Alanbrook
19b729afbf Merge remote-tracking branch 'origin/js-rm-with' into js-rm-modules 2025-06-16 14:09:29 -05:00
John Alanbrook
47729c225f Merge branch 'js-rm-ta' into js-rm-modules 2025-06-16 14:04:06 -05:00
John Alanbrook
ea6cf5db49 Merge remote-tracking branch 'origin/js-rm-class' into js-rm-modules 2025-06-16 14:01:12 -05:00
John Alanbrook
794baf8598 remove dynamic equality 2025-06-16 13:48:09 -05:00
John Alanbrook
a551368681 def now works as const 2025-06-16 08:07:45 -05:00
John Alanbrook
c20ca8c937 initial attempt 2025-06-15 19:48:48 -05:00
John Alanbrook
0217cf0da6 remove with 2025-06-15 19:40:58 -05:00
John Alanbrook
6bd7251933 initial rm 2025-06-15 10:30:58 -05:00
John Alanbrook
6dc8d97001 remove htmldda 2025-06-15 09:47:09 -05:00
John Alanbrook
9c0565d34f montecarlo faster; rm qjs-layout 2025-06-15 05:18:22 -05:00
John Alanbrook
0702e3495d finish remove? 2025-06-15 05:15:51 -05:00
John Alanbrook
108c39d22d initial attempt 2025-06-14 20:40:28 -05:00
John Alanbrook
946ebe5cd7 remove date intrinsic 2025-06-14 18:04:27 -05:00
John Alanbrook
fa12281ab9 add benchmarks 2025-06-14 17:03:49 -05:00
John Alanbrook
38a52fcb73 add tilemap 2025-06-13 20:48:33 -05:00
John Alanbrook
95a95e55e3 more details on cell 2025-06-13 20:42:53 -05:00
John Alanbrook
fc978a5766 add cell doc 2025-06-11 16:35:46 -05:00
John Alanbrook
9082ee2c47 move quickjs into source 2025-06-11 01:18:26 -05:00
John Alanbrook
b8ad8431f4 optimize 2025-06-11 01:13:58 -05:00
John Alanbrook
1c2b8228fe add num 2025-06-10 04:33:15 -05:00
John Alanbrook
a274fb174f faster text conversion to hex; guid generation now dealt with with -u.random_fit and blob formation; mersenne twister used for -u.random functions 2025-06-08 13:42:20 -05:00
John Alanbrook
3622a5ec58 actor messages now delivered as blobs 2025-06-08 11:06:59 -05:00
John Alanbrook
8a5f8a4d74 even faster wota encoding and decoding 2025-06-08 10:34:12 -05:00
John Alanbrook
c1d341eecd faster wota encoding 2025-06-08 08:35:12 -05:00
John Alanbrook
3176e6775d attempt for jswota in js 2025-06-08 00:49:19 -05:00
John Alanbrook
34dcd0a235 fix actors not running correctly 2025-06-07 23:35:47 -05:00
John Alanbrook
cbda7dfbc9 add utf8 and kim text encoder/decoders 2025-06-07 23:35:19 -05:00
John Alanbrook
d039e2cfe6 use spinlocks and other fixes 2025-06-07 17:09:03 -05:00
John Alanbrook
c02bd06ec0 signal kill works 2025-06-07 13:46:31 -05:00
John Alanbrook
efa63771e6 fix sockets 2025-06-07 12:24:34 -05:00
John Alanbrook
9f6d27fb3c fix multiple main thread actors not working 2025-06-06 21:26:29 -05:00
John Alanbrook
1a61ae6f77 actors keep running if they have messages, until no threads are available 2025-06-06 18:22:12 -05:00
John Alanbrook
83c816fd0e fix actors not freeing correctly if error in startup script 2025-06-06 17:42:28 -05:00
John Alanbrook
adbaa92dd5 fd now uses numbers to fix the inability to exit 2025-06-06 17:29:06 -05:00
John Alanbrook
580df9f233 add fstat; tests/cat as an example 2025-06-06 16:49:19 -05:00
John Alanbrook
d5d17560f9 add chunked reading; example with cat.ce 2025-06-06 16:15:39 -05:00
John Alanbrook
cd05ab97b5 Update agents directive 2025-06-06 14:59:30 -05:00
John Alanbrook
4eecbd692b fix actor not dying if killed while not in a turn 2025-06-06 13:59:14 -05:00
John Alanbrook
72beed7177 fix string leak with blob write_text 2025-06-06 13:58:46 -05:00
John Alanbrook
e0595de71a closes #19: kill underlings with system level interrupts 2025-06-06 12:24:32 -05:00
John Alanbrook
6687008d1a closes #14 2025-06-06 10:46:16 -05:00
John Alanbrook
5b9f1b8f51 closes #12 2025-06-06 10:31:41 -05:00
John Alanbrook
c570de7f41 closes #18: actor data now behind private symbol 2025-06-06 10:18:45 -05:00
John Alanbrook
d0138a6c23 ammend tests 2025-06-06 09:22:56 -05:00
John Alanbrook
29aa25e866 fix bug when calling unneeded inside of unneeded callback 2025-06-06 09:05:35 -05:00
John Alanbrook
ef28be93db fix js leak
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
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-06 08:42:16 -05:00
John Alanbrook
0d7be6a94e attempt fix 2025-06-05 16:19:06 -05:00
John Alanbrook
4fe78c4a63 move timer to its own file 2025-06-05 13:34:50 -05:00
John Alanbrook
b52edb2746 fix updated render loop 2025-06-05 12:04:45 -05:00
John Alanbrook
79d5412fe6 massively simplify loop logic 2025-06-05 01:08:27 -05:00
John Alanbrook
fcec2cd1dc sockets and fd 2025-06-04 22:28:06 -05:00
John Alanbrook
2038ce15a7 factor out sdl input
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
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-04 16:25:06 -05:00
John Alanbrook
08557011cb single threads; custom timer; letters
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
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-04 14:39:58 -05:00
John Alanbrook
3e87bfd6cc add to look for same folder name as well as main
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
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-04 13:22:58 -05:00
John Alanbrook
ef86dd3ecf remove some docstrings to save per actor memory 2025-06-03 23:58:44 -05:00
John Alanbrook
c887bcf7b9 no longer need to send ids to window renderer; per actor config 2025-06-03 16:13:54 -05:00
John Alanbrook
709f2459e4 add config options for ar timers 2025-06-03 14:33:51 -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
John Alanbrook
792da2ce4b Merge remote-tracking branch 'origin/misty'
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
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-24 22:24:01 -05:00
John Alanbrook
0c9d78a3d3 initialize parts of SDL only when required, and return errors
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
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-24 21:30:29 -05:00
John Alanbrook
e929f43a96 add steam module 2025-05-24 21:29:58 -05:00
John Alanbrook
01eff40690 auto latest docker image
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 2m15s
2025-05-24 09:37:29 -05:00
John Alanbrook
23813a4c31 http downloading in chunks 2025-05-24 00:55:56 -05:00
John Alanbrook
1248b94244 remove curl and openssl dependencies
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Successful in 1m44s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
2025-05-24 00:07:37 -05:00
John Alanbrook
f754d91e14 fix compilation errors on windows and linux
Some checks failed
Build and Deploy / build-macos (push) Failing after 4s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Failing after 1m59s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
2025-05-23 18:24:52 -05:00
John Alanbrook
7246016b8b example nat 2025-05-23 14:23:52 -05:00
John Alanbrook
b42eec96f6 separate the idea of misty actor and scene tree actor
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-linux (push) Failing after 1m30s
Build and Deploy / package-dist (push) Has been skipped
Build and Deploy / deploy-itch (push) Has been skipped
Build and Deploy / deploy-gitea (push) Has been skipped
2025-05-23 12:20:47 -05:00
John Alanbrook
efd98460c5 -u is no longer available as a global, only within the running actor code; enable passing args to use
Some checks failed
Build and Deploy / build-macos (push) Failing after 4s
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-22 18:59:57 -05:00
John Alanbrook
698dbd81ae removed unneeded functions and split out actor specific functions 2025-05-22 18:16:51 -05:00
John Alanbrook
13c2a0ba0c sprites, rtrees, and transforms made with constructor functions 2025-05-22 17:00:31 -05:00
John Alanbrook
d0fdb469dd rtrees are now created as a constructor 2025-05-22 16:30:48 -05:00
John Alanbrook
32366483dc split out debug 2025-05-22 16:17:11 -05:00
John Alanbrook
707b2845b1 split out spline and js 2025-05-22 16:05:42 -05:00
John Alanbrook
693087afae move rtree to its own module 2025-05-22 15:42:45 -05:00
John Alanbrook
51940080a8 fully compiles 2025-05-22 13:23:13 -05:00
John Alanbrook
a204fce4b5 initial refactor
Some checks failed
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-macos (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-22 11:48:27 -05:00
John Alanbrook
7bab2f1b7a clean up graphics
Some checks failed
Build and Deploy / build-linux (push) Failing after 1m32s
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-22 01:28:03 -05:00
John Alanbrook
f5ee3aada6 fix portal connection
Some checks failed
Build and Deploy / build-macos (push) Failing after 4s
Build and Deploy / build-linux (push) Failing after 1m34s
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-21 23:56:02 -05:00
John Alanbrook
85ee724754 add texture mode for renderer
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
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-11 09:34:31 -05:00
697 changed files with 118326 additions and 32867 deletions

19
.cell/cell.toml Normal file
View File

@@ -0,0 +1,19 @@
sdl_video = "main"
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master"
[system]
ar_timer = 60
actor_memory = 0
net_service = 0.1
reply_timeout = 60
actor_max = "10_000"
stack_max = 0
[actors]
[actors.prosperon/sdl_video]
main = true
[actors.prosperon/prosperon]
main = true
[actors.prosperon]
main = true
[actors.accio]
main=true

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"

12
.github/docker/Dockerfile.emscripten vendored Normal file
View File

@@ -0,0 +1,12 @@
# Use the official Emscripten SDK image (includes emcc, emsdk, Python, nodejs, etc)
FROM emscripten/emsdk:latest
# Install Meson & Ninja if needed (some emsdk tags already bundle them)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
meson \
ninja-build \
python3-pip && \
rm -rf /var/lib/apt/lists/*
RUN pip3 install --upgrade meson>=1.4

View File

@@ -28,5 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ccache \
mingw-w64 \
wine \
npm nodejs zip && \
rm -rf /var/lib/apt/lists/*
libmimalloc-dev \
npm nodejs zip
RUN apt-get install -y libunwind-dev libblas-dev liblapacke-dev

View File

@@ -1,4 +1,6 @@
FROM ubuntu:plucky
FROM debian:trixie
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y --no-install-recommends \
@@ -11,5 +13,11 @@ RUN apt-get update && \
pkg-config \
zip \
ccache \
npm nodejs && \
npm \
nodejs \
meson \
libmimalloc-dev \
libbsd-dev \
gcc-mingw-w64-ucrt64 \
g++-mingw-w64-ucrt64 && \
rm -rf /var/lib/apt/lists/*

View File

@@ -43,6 +43,35 @@ jobs:
with:
name: prosperon-artifacts-linux
path: build/prosperon
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Registry
uses: docker/login-action@v3
with:
registry: gitea.pockle.world
username: ${{ secrets.USER_GITEA }}
password: ${{ secrets.TOKEN_GITEA }}
- name: Determine Docker Tag
id: docker_tag
run: |
if [[ "${{ github.ref }}" =~ ^refs/tags/v.* ]]; then
TAG=$(echo "${{ github.ref }}" | sed 's#refs/tags/##')
echo "tag=$TAG" >> $GITHUB_OUTPUT
else
echo "tag=latest" >> $GITHUB_OUTPUT
fi
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: gitea.pockle.world/john/prosperon:${{ steps.docker_tag.outputs.tag }}
platforms: linux/amd64
# ──────────────────────────────────────────────────────────────
# WINDOWS BUILD (MSYS2 / CLANG64)

13
.gitignore vendored
View File

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

View File

@@ -13,7 +13,6 @@ This is a game engine developed using a QuickJS fork as its scripting language.
## Coding Practices
- Use K&R style C
- Use as little whitespace as possible
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
## Instructions

View File

@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Build Commands
### 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 release` - Build release version with LTO and optimizations
- `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
### Testing
- `meson test -C build_dbg` - Run all tests in debug build
- `meson test -C build_<variant>` - Run tests in specific build variant
- `./build_dbg/prosperon tests/<testname>.js` - Run specific test
- Available tests: `spawn_actor`, `empty`, `nota`, `wota`, `portalspawner`, `overling`, `send`, `delay`
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"
## Scripting language
This is called "cell", a variant of JavaScript with important differences. See docs/cell.md for detailed language documentation.
### Common development commands
- `meson setup build_<variant>` - Configure build directory
@@ -34,16 +34,20 @@ Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty sy
- Hierarchical actor system with spawning/killing
- Actor lifecycle: awake, update, draw, garbage collection
### JavaScript Style Guide
### Cell Language Style Guide
- Use `use()` function for imports (Misty-style, not ES6 import/export)
- Prefer closures and javascript objects and prototypes over ES6 style classes
- Follow existing JavaScript patterns in the codebase
- Functions as first-class citizens
- Do not use const or let; only var
- Use `def` for constants (not const)
- Use `var` for variables (block-scoped like let)
- Check for null with `== null` (no undefined in Cell)
- Use `==` for equality (always strict, no `===`)
- See docs/cell.md for complete language reference
### Core Systems
1. **Actor System** (scripts/core/engine.js)
- Message passing via `$_.send()`, `$_.receive()`
- Message passing via `send()`, `$_.receive()`
- Actor spawning/management
- Register-based component system (update, draw, gui, etc.)
@@ -99,7 +103,7 @@ cd examples/chess
- Documentation is found in docs
- Documentation for the JS modules loaded with 'use' is docs/api/modules
- .md files directly in docs gives a high level overview
- docs/dull is what this specific Javascript system is (including alterations from quickjs/es6)
- docs/cell.md documents the Cell language (JavaScript variant used in Prosperon)
### Shader Development
- Shaders are in `shaders/` directory as HLSL
@@ -126,7 +130,7 @@ meson test -C build_dbg
### Debugging
- Use debug build: `make debug`
- 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`
# Project Structure Notes
@@ -202,6 +206,11 @@ meson test -C build_dbg
### Utility Modules
- `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
- `json` - JSON parsing and serialization
- `util` - General utilities
@@ -223,10 +232,36 @@ meson test -C build_dbg
### Actor Pattern Usage
- Create actors with `actor.spawn(script, config)`
- Start actors with `$_.start(callback, script)` - the system automatically sends a greeting, callback receives {type: 'greet', actor: actor_ref}
- No need to manually send greetings - `$_.start` handles this automatically
- Manage actor hierarchy with overlings and underlings
- Schedule actor tasks with `delay()` method
- Schedule actor tasks with `$_.delay()` method
- Clean up with `kill()` and `garbage()`
### Actor Messaging with Callbacks
When sending a message with a callback, respond by sending to the message itself:
```javascript
// Sender side:
send(actor, {type: 'status'}, response => {
log.console(response); // Handle the response
});
// Receiver side:
$_.receiver(msg => {
if (msg.type === 'status') {
send(msg, {status: 'ok'}); // Send response to the message itself
}
});
```
**Critical Rules for Message Callbacks**:
- **A message can only be used ONCE as a send target** - after sending a response to a message, it cannot be used again
- If you need to send multiple updates (like progress), only the download request message should be used for the final response
- Status requests should each get their own individual response
- Actor objects and message headers are completely opaque - never try to access internal properties
- Never access `msg.__HEADER__` or similar - the actor system handles routing internally
- Use `$_.delay()` to schedule work and avoid blocking the message receiver
### Game Loop Registration
- Register functions like `update`, `draw`, `gui`, etc.
- Set function.layer property to control execution order
@@ -253,7 +288,7 @@ meson test -C build_dbg
- Custom formats: Aseprite animations, etc.
### Developer Tools
- Built-in documentation system with `prosperon.DOC`
- Built-in documentation system with `cell.DOC`
- Tracy profiler integration for performance monitoring
- Imgui debugging tools
- Console logging with various severity levels
@@ -267,12 +302,12 @@ Portals must reply with an actor object, not application data:
```javascript
// CORRECT: Portal replies with actor
$_.portal(e => {
$_.send(e, $_); // Reply with server actor
send(e, $_); // Reply with server actor
}, 5678);
// WRONG: Portal sends application data
$_.portal(e => {
$_.send(e, {type: 'game_start'}); // This breaks the pattern
send(e, {type: 'game_start'}); // This breaks the pattern
}, 5678);
```
@@ -289,21 +324,24 @@ Proper Misty networking follows a two-phase pattern:
- Normal bidirectional messaging begins
- Application logic handles game/service initialization
### Message Header Management
Messages contain `__HEADER__` information that can cause issues:
### Message Handling Best Practices
Messages should be treated as opaque objects with your application data:
```javascript
// CORRECT: Extract clean actor reference
$_.receiver(e => {
if (e.type === 'join_game') {
var opponent = e.__HEADER__.replycc; // Clean actor reference
$_.send(opponent, {type: 'game_start'});
// CORRECT: Store actor references separately
var players = {};
$_.receiver(msg => {
if (msg.type === 'join_game' && msg.player_id) {
// Store the message for later response
players[msg.player_id] = msg;
// Later, respond to the stored message
send(players[msg.player_id], {type: 'game_start'});
}
});
// WRONG: Using message object directly
$_.receiver(e => {
opponent = e; // Contains return headers that pollute future sends
// WRONG: Trying to access internal message properties
$_.receiver(msg => {
var sender = msg.__HEADER__.replycc; // Never do this!
});
```
@@ -323,7 +361,7 @@ Actor objects must be completely opaque black boxes that work identically regard
// - Network communication (uses ENet)
// The actor shouldn't know or care about the transport mechanism
$_.send(opponent, {type: 'move', from: [0,0], to: [1,1]});
send(opponent, {type: 'move', from: [0,0], to: [1,1]});
```
**Key Implementation Details:**
@@ -343,14 +381,14 @@ $_.send(opponent, {type: 'move', from: [0,0], to: [1,1]});
```javascript
// Server: Portal setup
$_.portal(e => {
$_.send(e, $_); // Just reply with actor
send(e, $_); // Just reply with actor
}, 5678);
// Client: Two-phase connection
$_.contact((actor, reason) => {
if (actor) {
opponent = actor;
$_.send(opponent, {type: 'join_game'}); // Phase 2: app messaging
send(opponent, {type: 'join_game'}); // Phase 2: app messaging
}
}, {address: "localhost", port: 5678});
@@ -358,7 +396,7 @@ $_.contact((actor, reason) => {
$_.receiver(e => {
if (e.type === 'join_game') {
opponent = e.__HEADER__.replycc;
$_.send(opponent, {type: 'game_start', your_color: 'black'});
send(opponent, {type: 'game_start', your_color: 'black'});
}
});
```

55
Dockerfile Normal file
View File

@@ -0,0 +1,55 @@
# Builder stage
FROM ubuntu:plucky AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip \
libmimalloc-dev \
libasound2-dev \
libpulse-dev \
libudev-dev \
libwayland-dev \
wayland-protocols \
libxkbcommon-dev \
libx11-dev \
libxext-dev \
libxrandr-dev \
libxcursor-dev \
libxi-dev \
libxinerama-dev \
libxss-dev \
libegl1-mesa-dev \
libgl1-mesa-dev \
cmake \
ninja-build \
git \
build-essential \
binutils \
pkg-config \
meson \
zip && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN git clone https://gitea.pockle.world/john/prosperon.git
WORKDIR /app/prosperon
RUN git checkout master
RUN meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
RUN meson compile -C build
# Runtime stage
FROM ubuntu:latest
# Install minimal runtime dependencies (e.g., for dynamically linked libraries)
RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/*
# Copy the compiled prosperon binary from the build stage
COPY --from=builder /app/prosperon/build/prosperon /usr/local/bin/prosperon
# Create an entrypoint script
RUN echo '#!/bin/bash' > /entrypoint.sh && \
echo '/usr/local/bin/prosperon "$@" &' >> /entrypoint.sh && \
echo 'tail -f /dev/null' >> /entrypoint.sh && \
chmod +x /entrypoint.sh
WORKDIR /workdir
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,22 +1,23 @@
debug: FORCE
meson setup build_dbg -Dbuildtype=debug
meson compile -C build_dbg
meson setup build_dbg -Dbuildtype=debugoptimized
meson install --only-changed -C build_dbg
cp build_dbg/cell . && chmod +x cell
fast: FORCE
meson setup build_fast
meson compile -C build_fast
meson install -C build_fast
release: FORCE
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
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
meson setup -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true build_small
meson compile -C build_small
meson install -C build_small
web: FORCE
meson setup -Deditor=false -Dbuildtype=minsize -Db_lto=true -Db_ndebug=true --cross-file emscripten.cross build_web
@@ -27,3 +28,43 @@ crosswin: FORCE
meson compile -C build_win
FORCE:
IMAGE_LINUX := prosperon/linux-builder:latest
IMAGE_MINGW := prosperon/mingw-builder:latest
IMAGE_EMSCRIPTEN := prosperon/emscripten-builder:latest
PWD := $(shell pwd)
ARTIFACTS_DIR := artifacts
build:
meson setup build -Dbuildtype=release -Db_lto=true -Db_lto_mode=thin -Db_ndebug=true
meson compile -C build
dockerclean:
rm -rf build build-win $(ARTIFACTS_DIR)
dockerlinux: build-linux-image run-linux
build-linux-image:
docker build -f .github/docker/Dockerfile.linux -t $(IMAGE_LINUX) .
run-linux:
@mkdir -p $(ARTIFACTS_DIR)/linux
docker run --rm -v $(PWD):/src -w /src $(IMAGE_LINUX) bash -lc 'meson setup build -Dbuildtype=release -Db_lto=true -Db_ndebug=true && meson compile -C build && cp build/cell $(ARTIFACTS_DIR)/linux/'
dockerwin: build-mingw-image run-win
build-mingw-image:
docker build -f .github/docker/Dockerfile.mingw -t $(IMAGE_MINGW) .
run-win:
@mkdir -p $(ARTIFACTS_DIR)/windows
docker run --rm -v $(PWD):/src -w /src $(IMAGE_MINGW) bash -lc 'meson setup build-win --cross-file mingw32.cross -Dbuildtype=release -Db_lto=true -Db_ndebug=true && meson compile -C build-win && cp build-win/cell.exe $(ARTIFACTS_DIR)/windows/'
dockeremc: build-emscripten-image run-emc
build-emscripten-image:
docker build -f .github/docker/Dockerfile.emscripten -t $(IMAGE_EMSCRIPTEN) .
run-emc:
@mkdir -p $(ARTIFACTS_DIR)/emscripten
docker run --rm -v $(PWD):/src -w /src $(IMAGE_EMSCRIPTEN) bash -lc 'meson setup build-emscripten --cross-file emscripten.cross -Dbuildtype=release -Db_ndebug=true -Ddefault_library=static -Dcpp_std=c++11 && meson compile -C build-emscripten && cp build-emscripten/cell.wasm build-emscripten/cell.js $(ARTIFACTS_DIR)/emscripten/'

43
benchmarks/binarytree.ce Normal file
View File

@@ -0,0 +1,43 @@
function mainThread() {
var maxDepth = Math.max(6, Number(arg[0] || 16));
var stretchDepth = maxDepth + 1;
var check = itemCheck(bottomUpTree(stretchDepth));
log.console(`stretch tree of depth ${stretchDepth}\t check: ${check}`);
var longLivedTree = bottomUpTree(maxDepth);
for (let depth = 4; depth <= maxDepth; depth += 2) {
var iterations = 1 << maxDepth - depth + 4;
work(iterations, depth);
}
log.console(`long lived tree of depth ${maxDepth}\t check: ${itemCheck(longLivedTree)}`);
}
function work(iterations, depth) {
let check = 0;
for (let i = 0; i < iterations; i++)
check += itemCheck(bottomUpTree(depth));
log.console(`${iterations}\t trees of depth ${depth}\t check: ${check}`);
}
function TreeNode(left, right) {
return {left, right};
}
function itemCheck(node) {
if (node.left == null)
return 1;
return 1 + itemCheck(node.left) + itemCheck(node.right);
}
function bottomUpTree(depth) {
return depth > 0
? new TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
: new TreeNode(null, null);
}
mainThread()
$_.stop()

View File

@@ -0,0 +1,24 @@
var blob = use('blob')
function eratosthenes (n) {
var sieve = new blob(n, true)
var sqrtN = Math.trunc(Math.sqrt(n));
for (i = 2; i <= sqrtN; i++)
if (sieve.read_logical(i))
for (j = i * i; j <= n; j += i)
sieve.write_bit(j, false);
return sieve;
}
var sieve = eratosthenes(10000000);
stone(sieve)
var c = 0
for (var i = 0; i < sieve.length; i++)
if (sieve.read_logical(i)) c++
log.console(c)
$_.stop()

58
benchmarks/fannkuch.ce Normal file
View File

@@ -0,0 +1,58 @@
function fannkuch(n) {
var perm1 = [n]
for (let i = 0; i < n; i++) perm1[i] = i
var perm = [n]
var count = [n]
var f = 0, flips = 0, nperm = 0, checksum = 0
var i, k, r
r = n
while (r > 0) {
i = 0
while (r != 1) { count[r-1] = r; r -= 1 }
while (i < n) { perm[i] = perm1[i]; i += 1 }
// Count flips and update max and checksum
f = 0
k = perm[0]
while (k != 0) {
i = 0
while (2*i < k) {
let t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
i += 1
}
k = perm[0]
f += 1
}
if (f > flips) flips = f
if ((nperm & 0x1) == 0) checksum += f; else checksum -= f
// Use incremental change to generate another permutation
var more = true
while (more) {
if (r == n) {
log.console( checksum )
return flips
}
let p0 = perm1[0]
i = 0
while (i < r) {
let j = i + 1
perm1[i] = perm1[j]
i = j
}
perm1[r] = p0
count[r] -= 1
if (count[r] > 0) more = false; else r += 1
}
nperm += 1
}
return flips;
}
var n = arg[0] || 10
log.console(`Pfannkuchen(${n}) = ${fannkuch(n)}`)
$_.stop()

16
benchmarks/fib.ce Normal file
View File

@@ -0,0 +1,16 @@
var time = use('time')
function fib(n) {
if (n<2) return n
return fib(n-1) + fib(n-2)
}
var now = time.number()
var arr = [1,2,3,4,5]
for (var i in arr) {
log.console(fib(28))
}
log.console(`elapsed: ${time.number()-now}`)
$_.stop()

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Run hyperfine with parameter lists
# This will create a cross-product of all libraries × all scenarios
hyperfine \
--warmup 3 \
--runs 20 \
-i \
--export-csv wota_vs_nota_vs_json.csv \
--export-json wota_vs_nota_vs_json.json \
--export-markdown wota_vs_nota_vs_json.md \
--parameter-list lib wota,nota,json \
--parameter-list scen empty,integers,floats,strings,objects,nested,large_array \
'cell benchmarks/wota_nota_json {lib} {scen}'
echo "Benchmark complete! Results saved to:"
echo " - wota_vs_nota_vs_json.csv"
echo " - wota_vs_nota_vs_json.json"
echo " - wota_vs_nota_vs_json.md"

395
benchmarks/js_perf.ce Normal file
View File

@@ -0,0 +1,395 @@
var time = use('time')
////////////////////////////////////////////////////////////////////////////////
// JavaScript Performance Benchmark Suite
// Tests core JS operations: property access, function calls, arithmetic, etc.
////////////////////////////////////////////////////////////////////////////////
// Test configurations
const iterations = {
simple: 10000000,
medium: 1000000,
complex: 100000
};
////////////////////////////////////////////////////////////////////////////////
// Utility: measureTime(fn) => how long fn() takes in seconds
////////////////////////////////////////////////////////////////////////////////
function measureTime(fn) {
var start = time.number();
fn();
var end = time.number();
return (end - start);
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Property Access
////////////////////////////////////////////////////////////////////////////////
function benchPropertyAccess() {
var obj = {
a: 1, b: 2, c: 3, d: 4, e: 5,
nested: { x: 10, y: 20, z: 30 }
};
var readTime = measureTime(function() {
var sum = 0;
for (var i = 0; i < iterations.simple; i++) {
sum += obj.a + obj.b + obj.c + obj.d + obj.e;
sum += obj.nested.x + obj.nested.y + obj.nested.z;
}
});
var writeTime = measureTime(function() {
for (var i = 0; i < iterations.simple; i++) {
obj.a = i;
obj.b = i + 1;
obj.c = i + 2;
obj.nested.x = i * 2;
obj.nested.y = i * 3;
}
});
return { readTime: readTime, writeTime: writeTime };
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Function Calls
////////////////////////////////////////////////////////////////////////////////
function benchFunctionCalls() {
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
function complexCalc(a, b, c) { return (a + b) * c / 2; }
var obj = {
method: function(x) { return x * 2; },
nested: {
deepMethod: function(x, y) { return x + y; }
}
};
var simpleCallTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.simple; i++) {
result = add(i, 1);
result = multiply(result, 2);
}
});
var methodCallTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.simple; i++) {
result = obj.method(i);
result = obj.nested.deepMethod(result, i);
}
});
var complexCallTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.medium; i++) {
result = complexCalc(i, i + 1, i + 2);
}
});
return {
simpleCallTime: simpleCallTime,
methodCallTime: methodCallTime,
complexCallTime: complexCallTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Array Operations
////////////////////////////////////////////////////////////////////////////////
function benchArrayOps() {
var pushTime = measureTime(function() {
var arr = [];
for (var i = 0; i < iterations.medium; i++) {
arr.push(i);
}
});
var arr = [];
for (var i = 0; i < 10000; i++) arr.push(i);
var accessTime = measureTime(function() {
var sum = 0;
for (var i = 0; i < iterations.medium; i++) {
sum += arr[i % 10000];
}
});
var iterateTime = measureTime(function() {
var sum = 0;
for (var j = 0; j < 1000; j++) {
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
});
return {
pushTime: pushTime,
accessTime: accessTime,
iterateTime: iterateTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Object Creation
////////////////////////////////////////////////////////////////////////////////
function benchObjectCreation() {
var literalTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var obj = { x: i, y: i * 2, z: i * 3 };
}
});
function Point(x, y) {
this.x = x;
this.y = y;
}
var constructorTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var p = new Point(i, i * 2);
}
});
var protoObj = {
x: 0,
y: 0,
move: function(dx, dy) {
this.x += dx;
this.y += dy;
}
};
var prototypeTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var obj = Object.create(protoObj);
obj.x = i;
obj.y = i * 2;
}
});
return {
literalTime: literalTime,
constructorTime: constructorTime,
prototypeTime: prototypeTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: String Operations
////////////////////////////////////////////////////////////////////////////////
function benchStringOps() {
var concatTime = measureTime(function() {
var str = "";
for (var i = 0; i < iterations.complex; i++) {
str = "test" + i + "value";
}
});
var strings = [];
for (var i = 0; i < 1000; i++) {
strings.push("string" + i);
}
var joinTime = measureTime(function() {
for (var i = 0; i < iterations.complex; i++) {
var result = strings.join(",");
}
});
var splitTime = measureTime(function() {
var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
for (var i = 0; i < iterations.medium; i++) {
var parts = str.split(",");
}
});
return {
concatTime: concatTime,
joinTime: joinTime,
splitTime: splitTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Arithmetic Operations
////////////////////////////////////////////////////////////////////////////////
function benchArithmetic() {
var intMathTime = measureTime(function() {
var result = 1;
for (var i = 0; i < iterations.simple; i++) {
result = ((result + i) * 2 - 1) / 3;
result = result % 1000 + 1;
}
});
var floatMathTime = measureTime(function() {
var result = 1.5;
for (var i = 0; i < iterations.simple; i++) {
result = Math.sin(result) + Math.cos(i * 0.01);
result = Math.sqrt(Math.abs(result)) + 0.1;
}
});
var bitwiseTime = measureTime(function() {
var result = 0;
for (var i = 0; i < iterations.simple; i++) {
result = (result ^ i) & 0xFFFF;
result = (result << 1) | (result >> 15);
}
});
return {
intMathTime: intMathTime,
floatMathTime: floatMathTime,
bitwiseTime: bitwiseTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Benchmark: Closure Operations
////////////////////////////////////////////////////////////////////////////////
function benchClosures() {
function makeAdder(x) {
return function(y) { return x + y; };
}
var closureCreateTime = measureTime(function() {
var funcs = [];
for (var i = 0; i < iterations.medium; i++) {
funcs.push(makeAdder(i));
}
});
var adders = [];
for (var i = 0; i < 1000; i++) {
adders.push(makeAdder(i));
}
var closureCallTime = measureTime(function() {
var sum = 0;
for (var i = 0; i < iterations.medium; i++) {
sum += adders[i % 1000](i);
}
});
return {
closureCreateTime: closureCreateTime,
closureCallTime: closureCallTime
};
}
////////////////////////////////////////////////////////////////////////////////
// Main benchmark runner
////////////////////////////////////////////////////////////////////////////////
log.console("JavaScript Performance Benchmark");
log.console("======================\n");
// Property Access
log.console("BENCHMARK: Property Access");
var propResults = benchPropertyAccess();
log.console(" Read time: " + propResults.readTime.toFixed(3) + "s => " +
(iterations.simple / propResults.readTime).toFixed(1) + " reads/sec [" +
(propResults.readTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Write time: " + propResults.writeTime.toFixed(3) + "s => " +
(iterations.simple / propResults.writeTime).toFixed(1) + " writes/sec [" +
(propResults.writeTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Function Calls
log.console("BENCHMARK: Function Calls");
var funcResults = benchFunctionCalls();
log.console(" Simple calls: " + funcResults.simpleCallTime.toFixed(3) + "s => " +
(iterations.simple / funcResults.simpleCallTime).toFixed(1) + " calls/sec [" +
(funcResults.simpleCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Method calls: " + funcResults.methodCallTime.toFixed(3) + "s => " +
(iterations.simple / funcResults.methodCallTime).toFixed(1) + " calls/sec [" +
(funcResults.methodCallTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Complex calls: " + funcResults.complexCallTime.toFixed(3) + "s => " +
(iterations.medium / funcResults.complexCallTime).toFixed(1) + " calls/sec [" +
(funcResults.complexCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Array Operations
log.console("BENCHMARK: Array Operations");
var arrayResults = benchArrayOps();
log.console(" Push: " + arrayResults.pushTime.toFixed(3) + "s => " +
(iterations.medium / arrayResults.pushTime).toFixed(1) + " pushes/sec [" +
(arrayResults.pushTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Access: " + arrayResults.accessTime.toFixed(3) + "s => " +
(iterations.medium / arrayResults.accessTime).toFixed(1) + " accesses/sec [" +
(arrayResults.accessTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Iterate: " + arrayResults.iterateTime.toFixed(3) + "s => " +
(1000 / arrayResults.iterateTime).toFixed(1) + " full iterations/sec");
log.console("");
// Object Creation
log.console("BENCHMARK: Object Creation");
var objResults = benchObjectCreation();
log.console(" Literal: " + objResults.literalTime.toFixed(3) + "s => " +
(iterations.medium / objResults.literalTime).toFixed(1) + " creates/sec [" +
(objResults.literalTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Constructor: " + objResults.constructorTime.toFixed(3) + "s => " +
(iterations.medium / objResults.constructorTime).toFixed(1) + " creates/sec [" +
(objResults.constructorTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Prototype: " + objResults.prototypeTime.toFixed(3) + "s => " +
(iterations.medium / objResults.prototypeTime).toFixed(1) + " creates/sec [" +
(objResults.prototypeTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
// String Operations
log.console("BENCHMARK: String Operations");
var strResults = benchStringOps();
log.console(" Concat: " + strResults.concatTime.toFixed(3) + "s => " +
(iterations.complex / strResults.concatTime).toFixed(1) + " concats/sec [" +
(strResults.concatTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
log.console(" Join: " + strResults.joinTime.toFixed(3) + "s => " +
(iterations.complex / strResults.joinTime).toFixed(1) + " joins/sec [" +
(strResults.joinTime / iterations.complex * 1e9).toFixed(1) + " ns/op]");
log.console(" Split: " + strResults.splitTime.toFixed(3) + "s => " +
(iterations.medium / strResults.splitTime).toFixed(1) + " splits/sec [" +
(strResults.splitTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Arithmetic Operations
log.console("BENCHMARK: Arithmetic Operations");
var mathResults = benchArithmetic();
log.console(" Integer math: " + mathResults.intMathTime.toFixed(3) + "s => " +
(iterations.simple / mathResults.intMathTime).toFixed(1) + " ops/sec [" +
(mathResults.intMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Float math: " + mathResults.floatMathTime.toFixed(3) + "s => " +
(iterations.simple / mathResults.floatMathTime).toFixed(1) + " ops/sec [" +
(mathResults.floatMathTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console(" Bitwise: " + mathResults.bitwiseTime.toFixed(3) + "s => " +
(iterations.simple / mathResults.bitwiseTime).toFixed(1) + " ops/sec [" +
(mathResults.bitwiseTime / iterations.simple * 1e9).toFixed(1) + " ns/op]");
log.console("");
// Closures
log.console("BENCHMARK: Closures");
var closureResults = benchClosures();
log.console(" Create: " + closureResults.closureCreateTime.toFixed(3) + "s => " +
(iterations.medium / closureResults.closureCreateTime).toFixed(1) + " creates/sec [" +
(closureResults.closureCreateTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console(" Call: " + closureResults.closureCallTime.toFixed(3) + "s => " +
(iterations.medium / closureResults.closureCallTime).toFixed(1) + " calls/sec [" +
(closureResults.closureCallTime / iterations.medium * 1e9).toFixed(1) + " ns/op]");
log.console("");
log.console("---------------------------------------------------------");
log.console("Benchmark complete.\n");
$_.stop()

40
benchmarks/mandelbrot.ce Normal file
View File

@@ -0,0 +1,40 @@
var blob = use('blob')
var iter = 50, limit = 2.0;
var zr, zi, cr, ci, tr, ti;
var h = Number(arg[0]) || 500
var w = h
log.console(`P4\n${w} ${h}`);
for (let y = 0; y < h; ++y) {
// Create a blob for the row - we need w bits
var row = new blob(w);
for (let x = 0; x < w; ++x) {
zr = zi = tr = ti = 0;
cr = 2 * x / w - 1.5;
ci = 2 * y / h - 1;
for (let i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
zi = 2 * zr * zi + ci;
zr = tr - ti + cr;
tr = zr * zr;
ti = zi * zi;
}
// Write a 1 bit if inside the set, 0 if outside
if (tr + ti <= limit * limit)
row.write_bit(1);
else
row.write_bit(0);
}
// Convert the blob to stone (immutable) to prepare for output
stone(row)
// Output the blob data as raw bytes
log.console(text(row, 'b'));
}
$_.stop()

11
benchmarks/montecarlo.ce Normal file
View File

@@ -0,0 +1,11 @@
var N = 1000000;
var num = 0;
for (var i = 0; i < N; i ++) {
var x = 2 * $_.random();
var y = $_.random();
if (y < Math.sin(x * x))
num++;
}
log.console(2 * num / N);
$_.stop()

188
benchmarks/nbody.ce Normal file
View File

@@ -0,0 +1,188 @@
var PI = Math.PI;
var SOLAR_MASS = 4 * PI * PI;
var DAYS_PER_YEAR = 365.24;
function Body(x, y, z, vx, vy, vz, mass) {
this.x = x;
this.y = y;
this.z = z;
this.vx = vx;
this.vy = vy;
this.vz = vz;
this.mass = mass;
}
function Jupiter() {
return new Body(
4.84143144246472090e+00,
-1.16032004402742839e+00,
-1.03622044471123109e-01,
1.66007664274403694e-03 * DAYS_PER_YEAR,
7.69901118419740425e-03 * DAYS_PER_YEAR,
-6.90460016972063023e-05 * DAYS_PER_YEAR,
9.54791938424326609e-04 * SOLAR_MASS
);
}
function Saturn() {
return new Body(
8.34336671824457987e+00,
4.12479856412430479e+00,
-4.03523417114321381e-01,
-2.76742510726862411e-03 * DAYS_PER_YEAR,
4.99852801234917238e-03 * DAYS_PER_YEAR,
2.30417297573763929e-05 * DAYS_PER_YEAR,
2.85885980666130812e-04 * SOLAR_MASS
);
}
function Uranus() {
return new Body(
1.28943695621391310e+01,
-1.51111514016986312e+01,
-2.23307578892655734e-01,
2.96460137564761618e-03 * DAYS_PER_YEAR,
2.37847173959480950e-03 * DAYS_PER_YEAR,
-2.96589568540237556e-05 * DAYS_PER_YEAR,
4.36624404335156298e-05 * SOLAR_MASS
);
}
function Neptune() {
return new Body(
1.53796971148509165e+01,
-2.59193146099879641e+01,
1.79258772950371181e-01,
2.68067772490389322e-03 * DAYS_PER_YEAR,
1.62824170038242295e-03 * DAYS_PER_YEAR,
-9.51592254519715870e-05 * DAYS_PER_YEAR,
5.15138902046611451e-05 * SOLAR_MASS
);
}
function Sun() {
return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
}
var bodies = Array(Sun(), Jupiter(), Saturn(), Uranus(), Neptune());
function offsetMomentum() {
var px = 0;
var py = 0;
var pz = 0;
var size = bodies.length;
for (var i = 0; i < size; i++) {
var body = bodies[i];
var mass = body.mass;
px += body.vx * mass;
py += body.vy * mass;
pz += body.vz * mass;
}
var body = bodies[0];
body.vx = -px / SOLAR_MASS;
body.vy = -py / SOLAR_MASS;
body.vz = -pz / SOLAR_MASS;
}
function advance(dt) {
var size = bodies.length;
for (var i = 0; i < size; i++) {
var bodyi = bodies[i];
var vxi = bodyi.vx;
var vyi = bodyi.vy;
var vzi = bodyi.vz;
for (var j = i + 1; j < size; j++) {
var bodyj = bodies[j];
var dx = bodyi.x - bodyj.x;
var dy = bodyi.y - bodyj.y;
var dz = bodyi.z - bodyj.z;
var d2 = dx * dx + dy * dy + dz * dz;
var mag = dt / (d2 * Math.sqrt(d2));
var massj = bodyj.mass;
vxi -= dx * massj * mag;
vyi -= dy * massj * mag;
vzi -= dz * massj * mag;
var massi = bodyi.mass;
bodyj.vx += dx * massi * mag;
bodyj.vy += dy * massi * mag;
bodyj.vz += dz * massi * mag;
}
bodyi.vx = vxi;
bodyi.vy = vyi;
bodyi.vz = vzi;
}
for (var i = 0; i < size; i++) {
var body = bodies[i];
body.x += dt * body.vx;
body.y += dt * body.vy;
body.z += dt * body.vz;
}
}
function energy() {
var e = 0;
var size = bodies.length;
for (var i = 0; i < size; i++) {
var bodyi = bodies[i];
e += 0.5 * bodyi.mass * ( bodyi.vx * bodyi.vx +
bodyi.vy * bodyi.vy + bodyi.vz * bodyi.vz );
for (var j = i + 1; j < size; j++) {
var bodyj = bodies[j];
var dx = bodyi.x - bodyj.x;
var dy = bodyi.y - bodyj.y;
var dz = bodyi.z - bodyj.z;
var distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
e -= (bodyi.mass * bodyj.mass) / distance;
}
}
return e;
}
var n = arg[0] || 100000
offsetMomentum();
log.console(`n = ${n}`)
log.console(energy().toFixed(9))
for (var i = 0; i < n; i++)
advance(0.01);
log.console(energy().toFixed(9))
var js = use('js')
// Get function metadata
var fn_info = js.fn_info(advance)
log.console(`${fn_info.filename}:${fn_info.line}:${fn_info.column}: function: ${fn_info.name}`)
// Display arguments
if (fn_info.args && fn_info.args.length > 0) {
log.console(` args: ${fn_info.args.join(' ')}`)
}
// Display local variables
if (fn_info.locals && fn_info.locals.length > 0) {
log.console(' locals:')
for (var i = 0; i < fn_info.locals.length; i++) {
var local = fn_info.locals[i]
log.console(` ${local.index}: ${local.type} ${local.name}`)
}
}
// Display stack size
log.console(` stack_size: ${fn_info.stack_size}`)
// Display disassembly
log.console(json.encode(js.disassemble(advance)))
log.console(js.disassemble(advance).length)
$_.stop()

76
benchmarks/nota.ce Normal file
View File

@@ -0,0 +1,76 @@
var nota = use('nota')
var os = use('os')
var io = use('io')
var ll = io.slurp('benchmarks/nota.json')
var newarr = []
var accstr = ""
for (var i = 0; i < 10000; i++) {
accstr += i;
newarr.push(i.toString())
}
// Arrays to store timing results
var jsonDecodeTimes = [];
var jsonEncodeTimes = [];
var notaEncodeTimes = [];
var notaDecodeTimes = [];
var notaSizes = [];
// Run 100 tests
for (let i = 0; i < 100; i++) {
// JSON Decode test
let start = os.now();
var jll = json.decode(ll);
jsonDecodeTimes.push((os.now() - start) * 1000);
// JSON Encode test
start = os.now();
let jsonStr = JSON.stringify(jll);
jsonEncodeTimes.push((os.now() - start) * 1000);
// NOTA Encode test
start = os.now();
var nll = nota.encode(jll);
notaEncodeTimes.push((os.now() - start) * 1000);
// NOTA Decode test
start = os.now();
var oll = nota.decode(nll);
notaDecodeTimes.push((os.now() - start) * 1000);
}
// Calculate statistics
function getStats(arr) {
def avg = arr.reduce((a, b) => a + b) / arr.length;
def min = Math.min(...arr);
def max = Math.max(...arr);
return { avg, min, max };
}
// Pretty print results
log.console("\n== Performance Test Results (100 iterations) ==");
log.console("\nJSON Decoding (ms):");
def jsonDecStats = getStats(jsonDecodeTimes);
log.console(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
log.console(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
log.console(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
log.console("\nJSON Encoding (ms):");
def jsonEncStats = getStats(jsonEncodeTimes);
log.console(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
log.console(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
log.console(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
log.console("\nNOTA Encoding (ms):");
def notaEncStats = getStats(notaEncodeTimes);
log.console(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
log.console(`Min: ${notaEncStats.min.toFixed(2)} ms`);
log.console(`Max: ${notaEncStats.max.toFixed(2)} ms`);
log.console("\nNOTA Decoding (ms):");
def notaDecStats = getStats(notaDecodeTimes);
log.console(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
log.console(`Min: ${notaDecStats.min.toFixed(2)} ms`);
log.console(`Max: ${notaDecStats.max.toFixed(2)} ms`);

View File

@@ -1,76 +0,0 @@
var nota = use('nota')
var os = use('os')
var io = use('io')
var ll = io.slurp('benchmarks/nota.json')
var newarr = []
var accstr = ""
for (var i = 0; i < 10000; i++) {
accstr += i;
newarr.push(i.toString())
}
// Arrays to store timing results
var jsonDecodeTimes = [];
var jsonEncodeTimes = [];
var notaEncodeTimes = [];
var notaDecodeTimes = [];
var notaSizes = [];
// Run 100 tests
for (let i = 0; i < 100; i++) {
// JSON Decode test
let start = os.now();
var jll = json.decode(ll);
jsonDecodeTimes.push((os.now() - start) * 1000);
// JSON Encode test
start = os.now();
let jsonStr = JSON.stringify(jll);
jsonEncodeTimes.push((os.now() - start) * 1000);
// NOTA Encode test
start = os.now();
var nll = nota.encode(jll);
notaEncodeTimes.push((os.now() - start) * 1000);
// NOTA Decode test
start = os.now();
var oll = nota.decode(nll);
notaDecodeTimes.push((os.now() - start) * 1000);
}
// Calculate statistics
function getStats(arr) {
const avg = arr.reduce((a, b) => a + b) / arr.length;
const min = Math.min(...arr);
const max = Math.max(...arr);
return { avg, min, max };
}
// Pretty print results
console.log("\n=== Performance Test Results (100 iterations) ===");
console.log("\nJSON Decoding (ms):");
const jsonDecStats = getStats(jsonDecodeTimes);
console.log(`Average: ${jsonDecStats.avg.toFixed(2)} ms`);
console.log(`Min: ${jsonDecStats.min.toFixed(2)} ms`);
console.log(`Max: ${jsonDecStats.max.toFixed(2)} ms`);
console.log("\nJSON Encoding (ms):");
const jsonEncStats = getStats(jsonEncodeTimes);
console.log(`Average: ${jsonEncStats.avg.toFixed(2)} ms`);
console.log(`Min: ${jsonEncStats.min.toFixed(2)} ms`);
console.log(`Max: ${jsonEncStats.max.toFixed(2)} ms`);
console.log("\nNOTA Encoding (ms):");
const notaEncStats = getStats(notaEncodeTimes);
console.log(`Average: ${notaEncStats.avg.toFixed(2)} ms`);
console.log(`Min: ${notaEncStats.min.toFixed(2)} ms`);
console.log(`Max: ${notaEncStats.max.toFixed(2)} ms`);
console.log("\nNOTA Decoding (ms):");
const notaDecStats = getStats(notaDecodeTimes);
console.log(`Average: ${notaDecStats.avg.toFixed(2)} ms`);
console.log(`Min: ${notaDecStats.min.toFixed(2)} ms`);
console.log(`Max: ${notaDecStats.max.toFixed(2)} ms`);

View File

@@ -0,0 +1,50 @@
function A(i,j) {
return 1/((i+j)*(i+j+1)/2+i+1);
}
function Au(u,v) {
for (var i=0; i<u.length; ++i) {
var t = 0;
for (var j=0; j<u.length; ++j)
t += A(i,j) * u[j];
v[i] = t;
}
}
function Atu(u,v) {
for (var i=0; i<u.length; ++i) {
var t = 0;
for (var j=0; j<u.length; ++j)
t += A(j,i) * u[j];
v[i] = t;
}
}
function AtAu(u,v,w) {
Au(u,w);
Atu(w,v);
}
function spectralnorm(n) {
var i, u=[], v=[], w=[], vv=0, vBv=0;
for (i=0; i<n; ++i)
u[i] = 1; v[i] = w[i] = 0;
for (i=0; i<10; ++i) {
AtAu(u,v,w);
AtAu(v,u,w);
}
for (i=0; i<n; ++i) {
vBv += u[i]*v[i];
vv += v[i]*v[i];
}
return Math.sqrt(vBv/vv);
}
log.console(spectralnorm(arg[0]).toFixed(9));
$_.stop()

View File

@@ -36,7 +36,7 @@ function roundTripWota(value) {
// iterations: how many times to loop
//
// You can tweak these as you like for heavier or lighter tests.
const benchmarks = [
def benchmarks = [
{
name: "Small Integers",
data: [0, 42, -1, 2023],
@@ -75,8 +75,8 @@ const benchmarks = [
];
// Print a header
console.log("Wota Encode/Decode Benchmark");
console.log("============================\n");
log.console("Wota Encode/Decode Benchmark");
log.console("===================\n");
// We'll run each benchmark scenario in turn.
for (let bench of benchmarks) {
@@ -96,11 +96,11 @@ for (let bench of benchmarks) {
let elapsedSec = measureTime(runAllData, bench.iterations);
let opsPerSec = (totalIterations / elapsedSec).toFixed(1);
console.log(`${bench.name}:`);
console.log(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
console.log(` Elapsed: ${elapsedSec.toFixed(3)} s`);
console.log(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
log.console(`${bench.name}:`);
log.console(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
log.console(` Elapsed: ${elapsedSec.toFixed(3)} s`);
log.console(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
}
// All done
console.log("Benchmark completed.\n");
log.console("Benchmark completed.\n");

View File

@@ -2,43 +2,53 @@
// benchmark_wota_nota_json.js
//
// Usage in QuickJS:
// qjs benchmark_wota_nota_json.js
// qjs benchmark_wota_nota_json.js <LibraryName> <ScenarioName>
//
// Ensure wota, nota, json, and os are all available, e.g.:
var wota = use('wota');
var nota = use('nota');
var json = use('json');
var jswota = use('jswota')
var os = use('os');
//
// Parse command line arguments
if (arg.length != 2) {
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');
$_.stop()
}
var lib_name = arg[0];
var scenario_name = arg[1];
////////////////////////////////////////////////////////////////////////////////
// 1. Setup "libraries" array to easily switch among Wota, Nota, and JSON
// 1. Setup "libraries" array to easily switch among wota, nota, and json
////////////////////////////////////////////////////////////////////////////////
const libraries = [
def libraries = [
{
name: "Wota",
name: "wota",
encode: wota.encode,
decode: wota.decode,
// Wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
// wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
getSize(encoded) {
return encoded.byteLength;
return encoded.length;
}
},
{
name: "Nota",
name: "nota",
encode: nota.encode,
decode: nota.decode,
// Nota also produces an ArrayBuffer:
// nota also produces an ArrayBuffer:
getSize(encoded) {
return encoded.byteLength;
return encoded.length;
}
},
{
name: "JSON",
name: "json",
encode: json.encode,
decode: json.decode,
// JSON produces a JS string. We'll measure its UTF-16 code unit length
// json produces a JS string. We'll measure its UTF-16 code unit length
// as a rough "size". Alternatively, you could convert to UTF-8 for
// a more accurate byte size. Here we just use `string.length`.
getSize(encodedStr) {
@@ -52,24 +62,29 @@ const libraries = [
// Each scenario has { name, data, iterations }
////////////////////////////////////////////////////////////////////////////////
const benchmarks = [
def benchmarks = [
{
name: "Small Integers",
name: "empty",
data: [{}, {}, {}, {}],
iterations: 10000
},
{
name: "integers",
data: [0, 42, -1, 2023],
iterations: 100000
},
{
name: "Floating point",
name: "floats",
data: [0.1, 1e-50, 3.14159265359],
iterations: 100000
},
{
name: "Strings (short, emoji)",
data: ["Hello, Wota!", "short", "Emoji: \u{1f600}\u{1f64f}"],
name: "strings",
data: ["Hello, wota!", "short", "Emoji: \u{1f600}\u{1f64f}"],
iterations: 100000
},
{
name: "Small Objects",
name: "objects",
data: [
{ a:1, b:2.2, c:"3", d:false },
{ x:42, y:null, z:"test" }
@@ -77,20 +92,15 @@ const benchmarks = [
iterations: 50000
},
{
name: "Nested Arrays",
name: "nested",
data: [ [ [ [1,2], [3,4] ] ], [[[]]], [1, [2, [3, [4]]]] ],
iterations: 50000
},
{
name: "Large Array (1k integers)",
name: "large_array",
data: [ Array.from({length:1000}, (_, i) => i) ],
iterations: 1000
},
{
name: "Large Binary Blob (256KB)",
data: [ new Uint8Array(256 * 1024).buffer ],
iterations: 200
}
];
////////////////////////////////////////////////////////////////////////////////
@@ -108,7 +118,7 @@ function measureTime(fn) {
// 4. For each library, we run each benchmark scenario and measure:
// - Encoding time (seconds)
// - Decoding time (seconds)
// - Total encoded size (bytes or code units for JSON)
// - Total encoded size (bytes or code units for json)
//
////////////////////////////////////////////////////////////////////////////////
@@ -125,11 +135,11 @@ function runBenchmarkForLibrary(lib, bench) {
let encodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) {
// For each data item, encode it
for (let d of bench.data) {
let e = lib.encode(d);
for (let j = 0; j < bench.data.length; j++) {
let e = lib.encode(bench.data[j]);
// 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.
if (i === 0) {
if (i == 0) {
encodedList.push(e);
totalSize += lib.getSize(e);
}
@@ -152,31 +162,43 @@ function runBenchmarkForLibrary(lib, bench) {
}
////////////////////////////////////////////////////////////////////////////////
// 5. Main driver: run across all benchmarks, for each library.
// 5. Main driver: run only the specified library and scenario
////////////////////////////////////////////////////////////////////////////////
console.log("Benchmark: Wota vs Nota vs JSON");
console.log("================================\n");
// Find the requested library and scenario
var lib = libraries.find(l => l.name == lib_name);
var bench = benchmarks.find(b => b.name == scenario_name);
for (let bench of benchmarks) {
console.log(`SCENARIO: ${bench.name}`);
console.log(` Data length: ${bench.data.length} | Iterations: ${bench.iterations}\n`);
for (let lib of libraries) {
let { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
// We'll compute total operations = bench.iterations * bench.data.length
let totalOps = bench.iterations * bench.data.length;
let encOpsPerSec = (totalOps / encodeTime).toFixed(1);
let decOpsPerSec = (totalOps / decodeTime).toFixed(1);
console.log(` ${lib.name}:`);
console.log(` Encode time: ${encodeTime.toFixed(3)}s => ${encOpsPerSec} encodes/sec`);
console.log(` Decode time: ${decodeTime.toFixed(3)}s => ${decOpsPerSec} decodes/sec`);
console.log(` Total size: ${totalSize} bytes (or code units for JSON)`);
console.log("");
}
console.log("---------------------------------------------------------\n");
if (!lib) {
log.console('Unknown library:', lib_name);
log.console('Available libraries:', libraries.map(l => l.name).join(', '));
$_.stop()
}
console.log("Benchmark complete.\n");
if (!bench) {
log.console('Unknown scenario:', scenario_name);
log.console('Available scenarios:', benchmarks.map(b => b.name).join(', '));
$_.stop()
}
// Run the benchmark for this library/scenario combination
var { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
// Output json for easy parsing by hyperfine or other tools
var totalOps = bench.iterations * bench.data.length;
var result = {
lib: lib_name,
scenario: scenario_name,
encodeTime: encodeTime,
decodeTime: decodeTime,
totalSize: totalSize,
totalOps: totalOps,
encodeOpsPerSec: totalOps / encodeTime,
decodeOpsPerSec: totalOps / decodeTime,
encodeNsPerOp: (encodeTime / totalOps) * 1e9,
decodeNsPerOp: (decodeTime / totalOps) * 1e9
};
log.console(json.encode(result));
$_.stop()

164
cell.md Normal file
View File

@@ -0,0 +1,164 @@
JAVASCRIPT VISION
I see objects as being a sort of combination of a lisp cell and a record: symbols, which are used internally, and are private and non iterable, and record string values, which are iterable, readable, and writable; of course everything becomes locked in when stone.
CELLSCRIPT
Javascript to its core. Objects. What does the language need? It can be quite small, I think. The key is, ANYTHING that we want to be fast and JIT'd, must be present. So, record lookups. These are actually quicker in a jit'd language that have them as a feature. Most things should be libraries. Blobs need to be in the runtime.
## Actors and Objects
Actors have a unique memory space and are made up of many objects. Objects are created in the Self style, but with a limitation: only one parent.
Actors only communicate with messages. Messages are a record of data consisting of a few base types: text, numbers, arrays, records, boolean values. There is no RPC, and it is not recommended to build it into your message passing protocol. Messages are very high level things: "do X", which the actor can then go and carry out.
Cell provides a fast way to condense an object for sending.
## How is it different from Javascript?
Cell condenses Javascript down into a few core ideas. There are three pillars which cell relies on:
1. The idea of actors as a method of communication between parts of a program.
2. The idea of objects as a way to organize and encapsulate data.
3. The idea of the capability model as security.
Javascript already supplied some of these things; Cell takes the core of Javascript and makes these ideas more explicit, and layers on the actor communication. It removes some goofy suckiness with javascript.
It acts as something like an operating system at the application level. It allows random code to be ran on your machine without worrying it will break something. This is built into the language.
It is completly dynamically typed. In comparison with C, in C, you can treat everything as everything: it is almost not typed at all. If you try to use a type as another type, no error is thrown; it might work, but it mightly silently not work. In Cell, data has a hard type, but if you use it "incorrectly", it will throw, and you can correct it. It's a live system.
Cell is linked very closely with C. It's best to think of cell as a layer for message passing on top of C. It is a way to describe how to translate C tasks from one section of the program to another - or to totally different computers (actors).
As such, cell's primary duty is marshalling data; so it has been designed for that to be as fast as possible. It has a syntax similar to C to make it easy to translate formulae from cell to C (or the other way, if desired).
Unlike many actor languages, Cell does not eschew assignment. You must have some assignment. However, when it comes to actor->actor communication, you do not assign. RPC is too direct: one actor should not care all that much what specific functions another actor has available. It should request it to do something, and get a result, or possibly not get a result. It doesn't care what the actor does as long as that gets done.
But within itself, it will assign; it must. Actors, or cells, are best thought of as computers or nodes within the internet. You request data from a URL by typing it into your browser; that computer you're attempting to reach may not even be on. It very likely has written some other data to disk whenever you contact it. But you're not doing the specific assigning. You just request data with HTTP commands.
## Objects and actors
Objects and actors are both similar ideas: they can hold data and respond to messages. Objects, local to an actor, can be thought of more like an RPC idea: they're invoked and return immediately. However, a failed RPC can crash an object; and in that case, the actor halts. It can be corrected.
## What does Cell bring you over C?
Programs which are built with C; they're built statically; they're built to not crash; they're built doing extremely low level things, like assignment.
The goal of cell is to thrust your C code into the parallel, actor realm. It lets your code crash and resume it; even rewriting the C code which is butressing your cell code and reloading it live.
There are two primary sorts of Cell modules you create from C code: data and IO. C code like
Where there were two similar things in javscript, one has been deleted and one kept. For example, there is only null now, no undefined. There are not four ways to test for equality; there is one.
The purpose of this is to be a great language for passing messages. So it should be fast at creating records first and foremost, and finding items on them. So it needs first class, jitt'd records.
Finally, it needs to use less memory. Deleting a bunch of this stuff should make that simpler.
What is present?
Objects, prototypes, numbers, arrays, strings, true, false, null.
Things to do:
merge typeof and instanceof. Misty has array? stone? number? etc; it needs to be generic. 5 is number returns true.
No new operator. It's the same idea though: simply instead of 'var guy = new sprite({x,y})' you would say 'var guy = sprite({x,y})', and sprite would simply be a function written to return a sprite object.
One number type. Dec64. Numeric stack can be added in later: a bigint library, for example, built inside cell.
Simplify the property attributes stuff. It is simple: objects have text keys and whatever values. Objects can also have objects as values. These work like symbols. You can share them, if desired. No well known symbols exist to eliminate that much misdirection. Obejcts basically work like private keys. If you serialize an object, objects that are keys are not serialized; only textual keys are. You can do something about it with a json() method that is invoked, if you desire. You cannot retrieve
var works like let; use var instead of let
no const
Function closures and _ => all work the same and close over the 'this' variable
Totally delete modules, coroutines, generators, proxy .. this deletes a lot of the big switch statement
Add the 'go' statement for tail calls
Add the 'do' statement
Implementation detail: separate out arrays and objects. They are not the same. Objects no longer need to track if they're fast arrays or not. They're not. Arrays are. Always.
Add the functional proxy idea. Log will be implemented through that.
Remove ===; it's just == now, and !=.
Remove 'continue'; now, break handles both. For a do statement, label it, and break to that label; so
var x = 0
do loop {
x++
if (x < 5) break loop // goes back to loop
break // exits loop
}
rename instanceof to 'is'
remove undefined; all are 'null' now
remove 'delete'; to remove a field, assign it to null
remove with
Remove Object. New records have a prototype of nothing. There are no more 'type prototypes' at all.
Arrays are their own type
Remove property descriptors. Properties are always settable, unless the object as a whole is stone. Stone is an object property instead of a shape property.
Syntax stuff .. would like to invoke functions without (). This can effectively simulate a "getter". Make ? and all other characters usable for names. No reserve words, which are endlessly irritating.
----
This will all actually come about gradually. Add a few things at a time, fix up code that did not adhere. For a lot of this, no new functions will even need to be written; it's a matter of not calling certain functions that are no longer relevant, or calling different functions when required.
## Benchmarks to implement
### general speed
binarytrees
coro-prime-sieve
edigits
fannkuch-redux
fasta
http-server
json serialize/deserialize
knucleotide
lru
mandelbrot
merkletrees
nbody
nsieve
pidigits
regex-redux
secp256k1
spectral-norm
### function calling and recursion stress - test goto
naive recursive fibonacci [fib(35) or fib(40)]
tak
ackermann
### numeric
sieve of eratosthenes [10^7 bits]
spectral norm [5500 x 5500 matrix]
n-body sim [50 000 - 100 000 steps]
mandelbrot [1600x1200 image, max iter = 50]
### memory & gc torture
binary trees [depth 18 (~500 000 nodes)]
richards task scheduler
fannkuch redux [n=11 or 12]
### dynamic object & property access
deltablue constraint solver
splay tree [256k nodes]
json, wota, nota decode->encode [use 2MB example]
### string / regex kernels
regex-DNA
fasta
word-frequency
### concurrency/message passing
ping-pong [two actors exhange a small record N times, 1M messages end to end]
chameneos [mating color swap game w/ randezvous]
For all, track memory and time.

View File

@@ -47,7 +47,7 @@ Certain functions are intrinsic to the program and cannot be overridden. They
- **Example**:
```js
this.delay(_ => {
console.log("3 seconds later!")
log.console("3 seconds later!")
}, 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".
Docstrings are set to the symbol `prosperon.DOC`
Docstrings are set to the symbol `cell.DOC`
```js
// 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
greet.doc = `
@@ -21,12 +21,12 @@ return greet
```js
// Another way is to add a docstring object to an object
var greet = {
hello() { console.log('hello!') }
hello() { log.console('hello!') }
}
greet[prosperon.DOC] = {}
greet[prosperon.DOC][prosperon.DOC] = 'An object full of different greeter functions'
greet[prosperon.DOC].hello = 'A greeter that says, "hello!"'
greet[cell.DOC] = {}
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
```

180
docs/cell.md Normal file
View File

@@ -0,0 +1,180 @@
# Cell Language
Cell is a JavaScript variant used in the Prosperon game engine. While very similar to JavaScript, it has several important differences that make it more suitable for game development and actor-based programming.
## Key Differences from JavaScript
### Null vs Undefined
- Cell has only `null`, no `undefined`
- Idiomatic null checking: `if (object.x == null)`
- Uninitialized variables and missing properties return `null`
### Equality Operators
- Only `==` operator exists (no `===`)
- `==` is always strict (no type coercion)
- `!=` for inequality (no `!==`)
### Variable Declarations
- `def` keyword for constants (replaces `const`)
- `var` works like `let` (block-scoped)
- No `let` keyword
### Compilation
- All code is compiled in strict mode
- No need for `"use strict"` directive
### Removed Features
Cell removes several JavaScript features for simplicity and security:
- No `Proxy` objects
- No ES6 module syntax (use `use()` function instead)
- No `class` syntax (use prototypes and closures)
- No `Reflect` API
- No `BigInt`
- No `WeakMap`, `WeakSet`, `WeakRef`
- No `document.all` (HTMLAllCollection)
- No `with` statement
- No `Date` intrinsic (use `time` module instead)
## Language Features
### Constants
```javascript
def PI = 3.14159
def MAX_PLAYERS = 4
// PI = 3.14 // Error: cannot reassign constant
```
### Variables
```javascript
var x = 10
{
var y = 20 // Block-scoped like let
x = 15 // Can access outer scope
}
// y is not accessible here
```
### Null Checking
```javascript
var obj = {name: "player"}
if (obj.score == null) {
obj.score = 0
}
```
### Functions
```javascript
// Function declaration
function add(a, b) {
return a + b
}
// Function expression
var multiply = function(a, b) {
return a * b
}
// Arrow functions work normally
var square = x => x * x
```
### Objects and Prototypes
```javascript
// Object creation
var player = {
x: 0,
y: 0,
move: function(dx, dy) {
this.x += dx
this.y += dy
}
}
// Prototype-based inheritance
function Enemy(x, y) {
this.x = x
this.y = y
}
Enemy.prototype.attack = function() {
// Attack logic
}
```
### Module System
Cell uses a custom module system with the `use()` function:
```javascript
var math = use('math')
var draw2d = use('prosperon/draw2d')
```
### Time Handling
Since there's no `Date` object, use the `time` module:
```javascript
var time = use('time')
var now = time.number() // Numeric timestamp
var record = time.record() // Structured time
var text = time.text() // Human-readable time
```
## Best Practices
1. **Prefer `def` for values that won't change**
```javascript
def TILE_SIZE = 32
var playerPos = {x: 0, y: 0}
```
2. **Always check for null explicitly**
```javascript
if (player.weapon == null) {
player.weapon = createDefaultWeapon()
}
```
3. **Use prototype patterns instead of classes**
```javascript
function GameObject(x, y) {
this.x = x
this.y = y
}
GameObject.prototype.update = function(dt) {
// Update logic
}
```
4. **Leverage closures for encapsulation**
```javascript
function createCounter() {
var count = 0
return {
increment: function() { count++ },
getValue: function() { return count }
}
}
```
## Common Gotchas
1. **No undefined means different behavior**
```javascript
var obj = {}
console.log(obj.missing) // null, not undefined
```
2. **Strict equality by default**
```javascript
"5" == 5 // false (no coercion)
null == 0 // false
```
3. **Block-scoped var**
```javascript
for (var i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 100) // Works as expected
}
```
## See Also
- [Actor Model](actor.md) - Cell's actor-based programming model
- [Module System](modules.md) - How to use and create modules
- [API Reference](api/index.md) - Complete API documentation

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:
```
console.log("Hello world")
log.console("Hello world")
this.delay(_ => {
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.
## 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.

View File

@@ -1,22 +1,22 @@
[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
pkg-config = 'pkg-config'
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
pkgconfig = 'pkg-config'
exe_wrapper = 'node'
[host_machine]
system = 'emscripten'
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'
cpu = 'wasm32'
endian = 'little'
[built-in options]
pkg_config_path = '$EMSDK/upstream/emscripten/cache/sysroot/lib/pkgconfig'
cmake_prefix_path = '$EMSDK/upstream/emscripten/cache/sysroot'
pkg_config_path = '/emsdk/upstream/emscripten/cache/sysroot/lib/pkgconfig'
cmake_prefix_path = '/emsdk/upstream/emscripten/cache/sysroot'
[properties]
needs_exe_wrapper = true
cmake_system_name = 'Emscripten'
sys_root = '@env:EMSDK@/upstream/emscripten/cache/sysroot'
needs_exe_wrapper = true
# <-- Replace with your real path to Emscripten.cmake:
cmake_toolchain_file = '/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake'

View File

@@ -1,3 +0,0 @@
def:
cd _prosperon && make && cp build_dbg/prosperon ..
./prosperon

View File

@@ -1,57 +0,0 @@
var CELLS = Symbol()
var key = function key(x,y) { return `${x},${y}` }
function grid(w, h)
{
this[CELLS] = new Map()
this.width = w;
this.height = h;
}
grid.prototype = {
cell(x,y) {
var k = key(x,y)
if (!this[CELLS].has(k)) this[CELLS].set(k,[])
return this[CELLS].get(k)
},
add(entity, pos) {
this.cell(pos.x, pos.y).push(entity);
entity.coord = pos.slice();
},
remove(entity, pos) {
var c = this.cell(pos.x, pos.y);
c.splice(c.indexOf(entity), 1);
},
at(pos) {
return this.cell(pos.x, pos.y);
},
inBounds(pos) {
return pos.x >= 0 && pos.x < this.width && pos.y >= 0 && pos.y < this.height;
},
each(fn) {
for (var [k, list] of this[CELLS])
for (var p of list) fn(p, p.coord);
},
toString() {
var out = `grid [${this.width}x${this.height}]
`
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var cell = this.at([x,y]);
out += cell.length
}
if (y !== this.height - 1) out += "\n"
}
return out
},
}
return grid

View File

@@ -1,45 +0,0 @@
/* helper robust coord access */
function cx(c) { return (c.x !== undefined) ? c.x : c[0]; }
function cy(c) { return (c.y !== undefined) ? c.y : c[1]; }
/* simple move-shape checks */
var deltas = {
pawn: function (pc, dx, dy, grid, to) {
var dir = (pc.colour === 'white') ? -1 : 1;
var base = (pc.colour === 'white') ? 6 : 1;
var one = (dy === dir && dx === 0 && grid.at(to).length === 0);
var two = (dy === 2 * dir && dx === 0 && cy(pc.coord) === base &&
grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir }).length === 0 &&
grid.at(to).length === 0);
var cap = (dy === dir && Math.abs(dx) === 1 && grid.at(to).length);
return one || two || cap;
},
rook : function (pc, dx, dy) { return (dx === 0 || dy === 0); },
bishop: function (pc, dx, dy) { return Math.abs(dx) === Math.abs(dy); },
queen : function (pc, dx, dy) { return (dx === 0 || dy === 0 || Math.abs(dx) === Math.abs(dy)); },
knight: function (pc, dx, dy) { return (Math.abs(dx) === 1 && Math.abs(dy) === 2) ||
(Math.abs(dx) === 2 && Math.abs(dy) === 1); },
king : function (pc, dx, dy) { return Math.max(Math.abs(dx), Math.abs(dy)) === 1; }
};
function clearLine(from, to, grid) {
var dx = Math.sign(cx(to) - cx(from));
var dy = Math.sign(cy(to) - cy(from));
var x = cx(from) + dx, y = cy(from) + dy;
while (x !== cx(to) || y !== cy(to)) {
if (grid.at({ x: x, y: y }).length) return false;
x += dx; y += dy;
}
return true;
}
function canMove(piece, from, to, grid) {
var dx = cx(to) - cx(from);
var dy = cy(to) - cy(from);
var f = deltas[piece.kind];
if (!f || !f(piece, dx, dy, grid, to)) return false;
if (piece.kind === 'knight') return true;
return clearLine(from, to, grid);
}
return { canMove };

View File

@@ -0,0 +1,234 @@
// HTTP Download Actor
// Handles download requests and progress queries
var http = use('http');
var os = use('os');
// Actor state
var state = {
downloading: false,
current_url: null,
total_bytes: 0,
downloaded_bytes: 0,
start_time: 0,
error: null,
connection: null,
download_msg: null,
chunks: []
};
// Helper to calculate progress percentage
function get_progress() {
if (state.total_bytes == 0) {
return 0;
}
return Math.round((state.downloaded_bytes / state.total_bytes) * 100);
}
// Helper to format status response
function get_status() {
if (!state.downloading) {
return {
status: 'idle',
error: state.error
};
}
var elapsed = os.now() - state.start_time;
var bytes_per_sec = elapsed > 0 ? state.downloaded_bytes / elapsed : 0;
return {
status: 'downloading',
url: state.current_url,
progress: get_progress(),
downloaded_bytes: state.downloaded_bytes,
total_bytes: state.total_bytes,
elapsed_seconds: elapsed,
bytes_per_second: Math.round(bytes_per_sec)
};
}
// Main message receiver
$_.receiver(function(msg) {
switch (msg.type) {
case 'download':
if (state.downloading) {
send(msg, {
type: 'error',
error: 'Already downloading',
current_url: state.current_url
});
return;
}
if (!msg.url) {
send(msg, {
type: 'error',
error: 'No URL provided'
});
return;
}
// Start download
state.downloading = true;
state.current_url = msg.url;
state.total_bytes = 0;
state.downloaded_bytes = 0;
state.start_time = os.now();
state.error = null;
state.download_msg = msg;
state.chunks = [];
try {
// Start the connection
state.connection = http.fetch_start(msg.url, msg.options || {});
if (!state.connection) {
throw new Error('Failed to start download');
}
// Schedule the first chunk read
$_.delay(read_next_chunk, 0);
} catch (e) {
state.error = e.toString();
state.downloading = false;
send(msg, {
type: 'error',
error: state.error,
url: msg.url
});
}
break;
case 'status':
log.console(`got status request. current is ${json.encode(get_status())}`)
send(msg, {
type: 'status_response',
...get_status()
});
break;
case 'cancel':
if (state.downloading) {
// Cancel the download
if (state.connection) {
http.fetch_close(state.connection);
state.connection = null;
}
state.downloading = false;
state.current_url = null;
state.download_msg = null;
state.chunks = [];
send(msg, {
type: 'cancelled',
message: 'Download cancelled',
url: state.current_url
});
} else {
send(msg, {
type: 'error',
error: 'No download in progress'
});
}
break;
default:
send(msg, {
type: 'error',
error: 'Unknown message type: ' + msg.type
});
}
});
// Non-blocking chunk reader
function read_next_chunk() {
if (!state.downloading || !state.connection) {
return;
}
try {
var chunk = http.fetch_read_chunk(state.connection);
if (chunk == null) {
// Download complete
finish_download();
return;
}
// Store chunk
state.chunks.push(chunk);
// Update progress
var info = http.fetch_info(state.connection);
state.downloaded_bytes = info.bytes_read;
if (info.headers_complete && info.content_length > 0) {
state.total_bytes = info.content_length;
}
// Schedule next chunk read
$_.delay(read_next_chunk, 0);
} catch (e) {
// Error during download
state.error = e.toString();
if (state.connection) {
http.fetch_close(state.connection);
}
if (state.download_msg) {
send(state.download_msg, {
type: 'error',
error: state.error,
url: state.current_url
});
}
// Reset state
state.downloading = false;
state.connection = null;
state.download_msg = null;
state.chunks = [];
}
}
// Complete the download and send result
function finish_download() {
if (state.connection) {
http.fetch_close(state.connection);
}
// Combine all chunks into single ArrayBuffer
var total_size = 0;
for (var i = 0; i < state.chunks.length; i++) {
total_size += state.chunks[i].byteLength;
}
var result = new ArrayBuffer(total_size);
var view = new Uint8Array(result);
var offset = 0;
for (var i = 0; i < state.chunks.length; i++) {
var chunk_view = new Uint8Array(state.chunks[i]);
view.set(chunk_view, offset);
offset += state.chunks[i].byteLength;
}
// Send complete message
if (state.download_msg) {
send(state.download_msg, {
type: 'complete',
url: state.current_url,
data: result,
size: result.byteLength,
duration: os.now() - state.start_time
});
}
// Reset state
state.downloading = false;
state.connection = null;
state.current_url = null;
state.download_msg = null;
state.chunks = [];
}

29
examples/nat.ce Normal file
View File

@@ -0,0 +1,29 @@
// NAT Punchthrough Server
// This server helps two chess clients find each other through NAT
// The server coordinates the punchthrough by having both clients
// connect to each other simultaneously
var json = use('json');
var waiting_client = null;
var match_id = 0;
$_.portal(e => {
log.console("NAT server: received connection request");
if (!is_actor(e.actor))
send(e, {reason: "Must provide the actor you want to connect."});
if (waiting_client) {
log.console(`sending out messages! to ${json.encode(e.actor)} and ${json.encode(waiting_client.actor)}`)
send(waiting_client, e.actor)
send(e, waiting_client.actor)
waiting_client = null
return
}
waiting_client = e
log.console(`actor ${json.encode(e.actor)} is waiting ...`)
}, 4000);

22
examples/nat_client.ce Normal file
View File

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

View File

@@ -1,31 +1,33 @@
project('prosperon', ['c', 'cpp'],
project('cell', ['c', 'cpp'],
version: '0.9.3',
meson_version: '>=1.4',
default_options : [ 'cpp_std=c++11'])
default_options : [ 'cpp_std=c++17'])
libtype = get_option('default_library')
link = []
src = []
fs = import('fs')
add_project_arguments('-pedantic', language: ['c'])
git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false)
prosperon_version = 'unknown'
cell_version = 'unknown'
if git_tag_cmd.returncode() == 0
prosperon_version = git_tag_cmd.stdout().strip()
cell_version = git_tag_cmd.stdout().strip()
endif
git_commit_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false)
prosperon_commit = 'unknown'
cell_commit = 'unknown'
if git_commit_cmd.returncode() == 0
prosperon_commit = git_commit_cmd.stdout().strip()
cell_commit = git_commit_cmd.stdout().strip()
endif
# Important: pass the definitions without double-escaping quotes
add_project_arguments(
'-DPROSPERON_VERSION="' + prosperon_version + '"',
'-DPROSPERON_COMMIT="' + prosperon_commit + '"',
'-DCELL_VERSION="' + cell_version + '"',
'-DCELL_COMMIT="' + cell_commit + '"',
language : 'c'
)
@@ -65,6 +67,32 @@ endif
cmake = import('cmake')
# Try to find system-installed mbedtls first
mbedtls_dep = dependency('mbedtls', static: true, required: false)
mbedx509_dep = dependency('mbedx509', static: true, required: false)
mbedcrypto_dep = dependency('mbedcrypto', static: true, required: false)
if not mbedtls_dep.found() or not mbedx509_dep.found() or not mbedcrypto_dep.found()
message('⚙ System mbedtls not found, building subproject...')
mbedtls_opts = cmake.subproject_options()
mbedtls_opts.add_cmake_defines({
'ENABLE_PROGRAMS': 'OFF', # Disable Mbed TLS programs
'ENABLE_TESTING': 'OFF', # Disable Mbed TLS tests
'CMAKE_BUILD_TYPE': 'Release', # Optimize for release
'MBEDTLS_FATAL_WARNINGS': 'ON', # Treat warnings as errors
'USE_STATIC_MBEDTLS_LIBRARY': 'ON',# Build static libraries
'USE_SHARED_MBEDTLS_LIBRARY': 'OFF'# Disable shared libraries
})
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_opts = cmake.subproject_options()
sdl3_opts.add_cmake_defines({
'SDL_STATIC': 'ON',
@@ -79,19 +107,28 @@ sdl3_opts.add_cmake_defines({
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')
# 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')]
# deps += cc.find_library('blas', required:true)
# deps += cc.find_library('lapacke', required:true)
endif
if host_machine.system() == 'windows'
deps += cc.find_library('d3d11')
deps += cc.find_library('ws2_32', required:true)
# For Windows, you may need to install OpenBLAS or Intel MKL
# and adjust these library names accordingly
# deps += cc.find_library('openblas', required:false)
# if not cc.find_library('openblas', required:false).found()
# deps += cc.find_library('blas', required:false)
# deps += cc.find_library('lapacke', required:false)
# endif
deps += cc.find_library('dbghelp')
deps += cc.find_library('winmm')
deps += cc.find_library('setupapi')
@@ -105,50 +142,204 @@ if host_machine.system() == 'windows'
endif
if host_machine.system() == 'emscripten'
link += '-sUSE_WEBGPU'
# Use the pre-installed copy
deps += dependency('sdl3',
static : true,
method : 'pkg-config', # or 'cmake' if you prefer
required : true)
message('⚙ Building SDL3 subproject for Emscripten...')
sdl3_opts.append_compile_args(
'c',
'-pthread',
'-sUSE_PTHREADS=1',
)
sdl3_opts.append_compile_args(
'cpp',
'-pthread',
'-sUSE_PTHREADS=1',
)
# 3. And into every link step
sdl3_opts.append_link_args(
'-pthread',
'-sUSE_PTHREADS=1',
'-sPTHREAD_POOL_SIZE=4',
)
sdl3_proj = cmake.subproject('sdl3', options: sdl3_opts)
deps += sdl3_proj.dependency('SDL3-static')
add_project_arguments('-DPATH_MAX=4096', language: 'c')
add_project_arguments(
'-pthread',
'-sUSE_PTHREADS=1',
'-sPTHREAD_POOL_SIZE=4',
language: ['c', 'cpp'])
add_project_link_arguments(
'--use-port=emdawnwebgpu',
'-sUSE_PTHREADS=1',
'-pthread',
'-sPTHREAD_POOL_SIZE=4',
'-sMALLOC=mimalloc',
language: ['c','cpp'])
else
sdl3_proj = cmake.subproject('sdl3', options : sdl3_opts)
deps += sdl3_proj.dependency('SDL3-static')
# Try to find system-installed SDL3 first
sdl3_dep = dependency('sdl3', static: true, required: false)
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
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
quickjs_opts = []
# Try to find system-installed physfs first
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
quickjs_opts += 'default_library=static'
deps += dependency('quickjs', static:true, default_options:quickjs_opts)
deps += dependency('qjs-layout', static:true)
deps += dependency('qjs-miniz', static:true)
deps += dependency('physfs', static:true)
deps += dependency('threads')
deps += dependency('chipmunk', static:true)
# Try to find system-installed chipmunk first
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'
deps += dependency('enet', static:true)
# Try to find system-installed enet first
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'
deps += dependency('mimalloc')
src += 'qjs_tracy.c'
tracy_opts = ['fibers=true', 'on_demand=true']
tracy_opts = ['fibers=true', 'no_exit=true', 'on_demand=true']
add_project_arguments('-DTRACY_ENABLE', language:['c','cpp'])
deps += dependency('tracy', static:true, default_options:tracy_opts)
# Try to find system-installed tracy first
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'
endif
deps += dependency('soloud', static:true)
deps += dependency('libqrencode', static:true)
# Try to find system-installed soloud first
soloud_dep = dependency('soloud', static: true, required: false)
if not soloud_dep.found()
message('⚙ System soloud not found, building subproject...')
deps += dependency('soloud', static:true)
else
deps += soloud_dep
endif
# Try to find system-installed qrencode first
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
# Always build for Steam unless it's Emscripten
if host_machine.system() == 'emscripten'
storefront = 'none'
else
storefront = 'steam'
endif
if storefront == 'steam'
steam_sdk_path = meson.current_source_dir() / 'sdk'
if host_machine.system() == 'darwin'
steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'osx' / 'libsteam_api.dylib'
elif host_machine.system() == 'linux'
steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'linux64' / 'libsteam_api.so'
elif host_machine.system() == 'windows'
steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'win64' / 'steam_api64.lib'
else
steam_lib_path = ''
endif
if fs.exists(steam_lib_path)
steam_dep = declare_dependency(
include_directories: include_directories('sdk/public'),
link_args: [steam_lib_path]
)
deps += steam_dep
src += 'qjs_steam.cpp'
message('Steam SDK enabled')
else
error('Steam SDK required but not found at: ' + steam_lib_path)
endif
else
add_project_arguments('-DNSTEAM', language: ['c', 'cpp'])
message('Storefront: ' + storefront)
endif
# Discord SDK integration
if host_machine.system() != 'emscripten'
discord_sdk_path = meson.current_source_dir() / 'discord_social_sdk'
if host_machine.system() == 'darwin'
discord_lib_path = discord_sdk_path / 'lib' / 'release' / 'libdiscord_partner_sdk.dylib'
elif host_machine.system() == 'linux'
discord_lib_path = discord_sdk_path / 'lib' / 'release' / 'libdiscord_partner_sdk.so'
elif host_machine.system() == 'windows'
discord_lib_path = discord_sdk_path / 'lib' / 'release' / 'discord_partner_sdk.lib'
else
discord_lib_path = ''
endif
if fs.exists(discord_lib_path)
discord_dep = declare_dependency(
include_directories: include_directories('discord_social_sdk/include'),
link_args: [discord_lib_path]
)
deps += discord_dep
src += 'qjs_discord.cpp'
message('Discord SDK enabled')
else
add_project_arguments('-NDISCORD', language: ['c', 'cpp'])
message('Discord SDK not found at: ' + discord_lib_path)
endif
else
add_project_arguments('-NDISCORD', language: ['c', 'cpp'])
message('Discord SDK disabled for Emscripten')
endif
link_args = link
sources = []
src += [
'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',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c'
'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_layout.c'
]
# quirc src
src += [
@@ -156,87 +347,23 @@ src += [
'thirdparty/quirc/identify.c', 'thirdparty/quirc/version_db.c'
]
curl_opts = [
'http=enabled',
'ssl=enabled',
'openssl=enabled',
'schannel=disabled',
'secure-transport=disabled',
'dict=disabled',
'file=disabled',
'ftp=disabled',
'gopher=disabled',
'imap=disabled',
'ldap=disabled',
'ldaps=disabled',
'mqtt=disabled',
'pop3=disabled',
'rtmp=disabled',
'rtsp=disabled',
'smb=disabled',
'smtp=disabled',
'telnet=disabled',
'tftp=disabled',
'alt-svc=disabled',
'asynchdns=disabled',
'aws=disabled',
'basic-auth=disabled',
'bearer-auth=disabled',
'bindlocal=disabled',
'brotli=disabled',
'cookies=disabled',
'digest-auth=disabled',
'doh=disabled',
'form-api=disabled',
'getoptions=disabled',
'gsasl=disabled',
'gss-api=disabled',
'headers-api=disabled',
'hsts=disabled',
'http2=disabled',
'idn=disabled',
'kerberos-auth=disabled',
'libcurl-option=disabled',
'libz=disabled',
'mime=disabled',
'negotiate-auth=disabled',
'netrc=disabled',
'ntlm=disabled',
'parsedate=disabled',
'progress-meter=disabled',
'proxy=disabled',
'psl=disabled',
'sha512_256=disabled',
'shuffle-dns=disabled',
'socketpair=disabled',
'tls-srp=disabled',
'unixsockets=disabled',
'verbose-strings=disabled',
'zstd=disabled',
'debug=disabled',
'curldebug=false',
'libuv=disabled',
'tests=disabled',
'unittests=disabled',
'default_library=static'
]
curl_proj = subproject('curl', default_options: curl_opts)
deps += dependency('libcurl')
deps += dependency('zlib', static: true)
deps += dependency('openssl', static:true)
# quickjs src
src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
imsrc = [
'GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp',
'imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp',
'implot_items.cpp','implot.cpp','imgui_impl_sdlrenderer3.cpp','imgui_impl_sdl3.cpp',
'imgui_impl_sdlgpu3.cpp'
'imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','backends/imgui_impl_sdl3.cpp', 'backends/imgui_impl_sdlrenderer3.cpp', 'backends/imgui_impl_sdlgpu3.cpp'
]
foreach file : imsrc
sources += 'source/thirdparty/imgui' / file
sources += 'source/qjs_imgui.cpp'
endforeach
srceng = 'source'
tp = srceng / 'thirdparty'
includes = [
srceng, tp / 'cgltf', tp / 'imgui', tp / 'par', tp / 'stb',
srceng, tp / 'cgltf', tp / 'imgui', tp / 'imgui/backends', tp / 'par', tp / 'stb',
tp, tp / 'pl_mpeg/include', tp / 'quirc'
]
@@ -245,99 +372,33 @@ foreach file : src
sources += files(full_path)
endforeach
if get_option('editor')
sources += 'source/qjs_imgui.cpp'
foreach imgui : imsrc
sources += tp / 'imgui' / imgui
endforeach
endif
includers = []
foreach inc : includes
includers += include_directories(inc)
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'
exe_ext = '.exe'
else
exe_ext = ''
endif
prosperon = custom_target('prosperon',
output: 'prosperon' + exe_ext,
input: [exe_for_concat, core],
command: [
'sh', '-c',
'cat "$1" "$2" > "$3" && chmod +x "$3" >/dev/null 2>&1',
'concat',
'@INPUT0@',
'@INPUT1@',
'@OUTPUT@'
],
build_always_stale: true,
build_by_default: true
strip_enabled = ['release', 'minsize'].contains(get_option('buildtype'))
if strip_enabled
add_project_link_arguments('-s', language: ['c', 'cpp'])
endif
cell = executable('cell', sources,
dependencies: deps,
include_directories: includers,
link_args: link,
build_rpath: '$ORIGIN',
install: true
)
prosperon_dep = declare_dependency(
link_with: prosperon
)
copy_tests = custom_target(
'copy_tests',
output: 'tests',
command: [
'cp', '-rf',
join_paths(meson.project_source_root(), 'tests'),
meson.project_build_root()
],
build_always_stale: true,
build_by_default: true
cell_dep = declare_dependency(
link_with: cell
)
tests = [
@@ -352,5 +413,5 @@ tests = [
]
foreach file : tests
test(file, prosperon_raw, args:['tests/' + file + '.js'], depends:copy_tests)
test(file, cell, args:['tests/' + file])
endforeach

View File

@@ -1,4 +1,3 @@
option('editor', type:'boolean', value:true)
option('chipmunk', type:'boolean', value:true)
option('enet', type:'boolean', value:true)
option('storefront', type:'combo', choices:['none','steam', 'gog', 'egs'], value:'none')

View File

@@ -1,7 +1,8 @@
var graphics = use('graphics')
var color = use('color')
var sprite = {
image: undefined,
image: null,
set color(x) { this._sprite.color = x; },
get color() { return this._sprite.color; },
anim_speed: 1,
@@ -11,7 +12,7 @@ var sprite = {
return;
}
if (typeof str === 'string') {
if (typeof str == 'string') {
if (!this.animset[str]) {
fn?.();
return;
@@ -25,8 +26,8 @@ var sprite = {
this.del_anim?.();
this.del_anim = () => {
this.del_anim = undefined;
advance = undefined;
this.del_anim = null;
advance = null;
stop?.();
};
@@ -37,10 +38,10 @@ var sprite = {
var done = false;
if (reverse) {
f = (((f - 1) % playing.frames.length) + playing.frames.length) % playing.frames.length;
if (f === playing.frames.length - 1) done = true;
if (f == playing.frames.length - 1) done = true;
} else {
f = (f + 1) % playing.frames.length;
if (f === 0) done = true;
if (f == 0) done = true;
}
this.image = playing.frames[f];
@@ -65,7 +66,7 @@ var sprite = {
set path(p) {
var image = graphics.texture(p);
if (!image) {
console.warn(`Could not find image ${p}.`);
log.warn(`Could not find image ${p}.`);
return;
}
@@ -101,14 +102,14 @@ var sprite = {
},
garbage: function() {
this.del_anim?.();
this.anim = undefined;
this.anim = null;
tree.delete(this._sprite)
this.transform.parent = undefined
this.transform.parent = null
for (var t of this.transform.children())
t.parent = undefined
t.parent = null
delete this.transform.sprite
delete this._sprite
// console.log("CLEARED SPRITE")
// log.console("CLEARED SPRITE")
},
anchor: [0, 0],
set layer(v) { this._sprite.layer = v; },
@@ -184,7 +185,9 @@ sprite.inputs.kp1 = function () {
this.setanchor("ul");
};
var tree = graphics.make_rtree()
var rtree = use('rtree')
var tree = new rtree
sprite.tree = tree;
var IN = Symbol()
@@ -213,7 +216,7 @@ sprite.to_queue = function(ysort = false)
};
var culled = sprite.tree.query(camrect)
if (culled.length == 0) return [];
var cmd = graphics.make_sprite_queue(culled, prosperon.camera, undefined, 1);
var cmd = graphics.make_sprite_queue(culled, prosperon.camera, null, 1);
return cmd;
}
@@ -221,18 +224,18 @@ return sprite;
---
var Color = use('color')
var os = use('os')
var graphics = use('graphics')
var color = use('color')
var transform = use('transform')
var sprite = use('sprite')
this.transform = os.make_transform();
this.transform = new transform;
if (this.overling.transform)
this.transform.parent = this.overling.transform;
this.transform.change_hook = $.t_hook;
var msp = graphics.make_sprite();
var msp = new sprite
this._sprite = msp;
msp.color = Color.white;
msp.color = color.white;
this.transform.sprite = this

View File

@@ -1,6 +1,6 @@
var cam = {}
var os = use('os')
var transform = use('transform')
var basecam = {}
basecam.draw_rect = function(size)
@@ -88,7 +88,7 @@ function mode_rect(src,dst,mode = "stretch")
cam.make = function()
{
var c = Object.create(basecam)
c.transform = os.make_transform()
c.transform = new transform
c.transform.unit()
c.zoom = 1
c.size = [640,360]

View File

@@ -3,32 +3,49 @@
var layout = use('layout')
var geometry = use('geometry')
var draw = use('draw2d')
var draw = use('prosperon/draw2d')
var graphics = use('graphics')
var util = use('util')
var input = use('input')
function normalizeSpacing(spacing) {
if (typeof spacing == 'number') {
return {l: spacing, r: spacing, t: spacing, b: spacing}
} else if (Array.isArray(spacing)) {
if (spacing.length == 2) {
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
} else if (spacing.length == 4) {
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
}
} else if (typeof spacing == 'object') {
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
} else {
return {l:0, r:0, t:0, b:0}
}
}
var lay_ctx = layout.make_context();
var clay_base = {
font: undefined,
background_image: undefined,
font: null,
background_image: null,
slice: 0,
font: 'smalle.16',
font_size: undefined,
color: [1,1,1,1],
font_size: null,
color: {r:1,g:1,b:1,a:1},
spacing:0,
padding:0,
margin:0,
offset:[0,0],
size:undefined,
background_color: undefined
offset:{x:0, y:0},
size:null,
background_color: null
};
var root_item;
var root_config;
var boxes = [];
var clay = {}
var focused_textbox = null
clay.behave = layout.behave;
clay.contain = layout.contain;
@@ -38,6 +55,10 @@ clay.draw = function draw(size, fn, config = {})
lay_ctx.reset();
boxes = [];
var root = lay_ctx.item();
// Accept both array and object formats
if (Array.isArray(size)) {
size = {width: size[0], height: size[1]};
}
lay_ctx.set_size(root,size);
lay_ctx.set_contain(root,layout.contain.row);
root_item = root;
@@ -55,7 +76,7 @@ clay.draw = function draw(size, fn, config = {})
box.content = lay_ctx.get_rect(box.id);
box.boundingbox = Object.assign({}, box.content);
var padding = util.normalizeSpacing(box.config.padding || 0);
var padding = normalizeSpacing(box.config.padding || 0);
if (padding.l || padding.r || padding.t || padding.b) {
// Adjust the boundingbox to include the padding
box.boundingbox.x -= padding.l;
@@ -64,15 +85,15 @@ clay.draw = function draw(size, fn, config = {})
box.boundingbox.height += padding.t + padding.b;
}
box.marginbox = Object.assign({}, box.content);
var margin = util.normalizeSpacing(box.config.margin || 0);
var margin = normalizeSpacing(box.config.margin || 0);
box.marginbox.x -= margin.l;
box.marginbox.y -= margin.t;
box.marginbox.width += margin.l+margin.r;
box.marginbox.height += margin.t+margin.b;
box.content.y *= -1;
box.content.y += size.y;
box.content.y += size.height;
box.boundingbox.y *= -1;
box.boundingbox.y += size.y;
box.boundingbox.y += size.height;
box.content.anchor_y = 1;
box.boundingbox.anchor_y = 1;
}
@@ -112,21 +133,22 @@ clay.spacer = create_view_fn({
function image_size(img)
{
return [img.rect[2]*img.texture.width, img.rect[3]*img.texture.height];
return [img.width * (img.rect?.width || 1), img.height * (img.rect?.height || 1)];
}
function add_item(config)
{
// Normalize the child's margin
var margin = util.normalizeSpacing(config.margin || 0);
var padding = util.normalizeSpacing(config.padding || 0);
var margin = normalizeSpacing(config.margin || 0);
var padding = normalizeSpacing(config.padding || 0);
var childGap = root_config.child_gap || 0;
// Adjust for child_gap
root_config._childIndex ??= 0
if (root_config._childIndex > 0) {
var parentContain = root_config.contain || 0;
var isVStack = (parentContain & layout.contain.column) !== 0;
var isHStack = (parentContain & layout.contain.row) !== 0;
var isVStack = (parentContain & layout.contain.column) != 0;
var isHStack = (parentContain & layout.contain.row) != 0;
if (isVStack) {
margin.t += childGap;
@@ -146,6 +168,11 @@ function add_item(config)
var item = lay_ctx.item();
lay_ctx.set_margins(item, use_config.margin);
use_config.size ??= {width:0, height:0}
// Convert array to object if needed
if (Array.isArray(use_config.size)) {
use_config.size = {width: use_config.size[0], height: use_config.size[1]};
}
lay_ctx.set_size(item,use_config.size);
lay_ctx.set_contain(item,use_config.contain);
lay_ctx.set_behave(item,use_config.behave);
@@ -162,7 +189,7 @@ function add_item(config)
function rectify_configs(config_array)
{
if (config_array.length === 0)
if (config_array.length == 0)
config_array = [{}];
for (var i = config_array.length-1; i > 0; i--)
@@ -178,8 +205,8 @@ clay.image = function image(path, ...configs)
{
var config = rectify_configs(configs);
var image = graphics.texture(path);
config.image = image;
config.size ??= [image.texture.width, image.texture.height];
config.image = path; // Store the path string, not the texture object
config.size ??= {width: image.width, height: image.height};
add_item(config);
}
@@ -188,7 +215,9 @@ clay.text = function text(str, ...configs)
var config = rectify_configs(configs);
config.size ??= [0,0];
config.font = graphics.get_font(config.font)
var tsize = config.font.text_size(str, 0, 0, config.size.x);
var tsize = graphics.font_text_size(config.font, str, 0, config.size.x);
tsize.x = Math.ceil(tsize.x)
tsize.y = Math.ceil(tsize.y)
config.size = config.size.map((x,i) => Math.max(x, tsize[i]));
config.text = str;
add_item(config);
@@ -210,41 +239,53 @@ clay.button = function button(str, action, config = {})
{
config.__proto__ = button_base;
config.font = graphics.get_font(config.font)
config.size = config.font.text_size(str)
config.size = graphics.font_text_size(config.font, str, 0, 0)
add_item(config);
config.text = str;
config.action = action;
}
var hovered = undefined;
clay.newframe = function() { hovered = undefined; }
clay.textbox = function(str, on_change, ...configs) {
var config = rectify_configs(configs)
config.on_change = on_change
config.text = str
config.font = graphics.get_font(config.font)
var tsize = graphics.font_text_size(config.font, str, 0, 0)
config.size ??= [0,0]
config.size = [Math.ceil(tsize.x), Math.ceil(tsize.y)]
config.size = [Math.max(config.size[0], config.size[0]), Math.max(config.size[1], config.size[1])]
add_item(config)
}
// mousepos given in hud coordinates
clay.draw_commands = function draw_commands(cmds, pos = [0,0], mousepos = prosperon.camera.screen2hud(input.mouse.screenpos()))
var point = use('point')
// Pure rendering function - no input handling
clay.draw_commands = function draw_commands(cmds, pos = {x:0,y:0})
{
for (var cmd of cmds) {
var config = cmd.config;
var boundingbox = geometry.rect_move(cmd.boundingbox,pos.add(config.offset));
var content = geometry.rect_move(cmd.content,pos.add(config.offset));
if (config.hovered && geometry.rect_point_inside(boundingbox, mousepos)) {
config.hovered.__proto__ = config;
config = config.hovered;
hovered = config;
var config = cmd.config
var boundingbox = geometry.rect_move(cmd.boundingbox,point.add(pos,config.offset))
var content = geometry.rect_move(cmd.content,point.add(pos, config.offset))
// Check if this box should use hover styling
if (cmd.state && cmd.state.hovered && config.hovered) {
config.hovered.__proto__ = config
config = config.hovered
}
if (config.background_image)
if (config.slice)
draw.slice9(config.background_image, boundingbox, config.slice, config.background_color);
draw.slice9(config.background_image, boundingbox, config.slice, config.background_color)
else
draw.image(config.background_image, boundingbox, 0, config.color);
draw.image(config.background_image, boundingbox, 0, config.color)
else if (config.background_color)
draw.rectangle(boundingbox, config.background_color);
draw.rectangle(boundingbox, config.background_color)
if (config.text)
draw.text(config.text, content, config.font, config.font_size, config.color, config.size.x);
draw.text(config.text, content, config.font, config.color, config.size.width)
if (config.image)
draw.image(config.image, content, 0, config.color);
draw.image(config.image, content, 0, config.color)
}
}
@@ -253,7 +294,7 @@ clay.debug_colors = dbg_colors;
dbg_colors.content = [1,0,0,0.1];
dbg_colors.boundingbox = [0,1,0,0,0.1];
dbg_colors.margin = [0,0,1,0.1];
clay.draw_debug = function draw_debug(cmds, pos = [0,0])
clay.draw_debug = function draw_debug(cmds, pos = {x:0, y:0})
{
for (var i = 0; i < cmds.length; i++) {
var cmd = cmds[i];
@@ -265,11 +306,4 @@ clay.draw_debug = function draw_debug(cmds, pos = [0,0])
}
}
clay.inputs = {};
clay.inputs.mouse = {}
clay.inputs.mouse.left = function()
{
if (hovered && hovered.action) hovered.action();
}
return clay

63
prosperon/clay_input.cm Normal file
View File

@@ -0,0 +1,63 @@
// clay_input.cm - Input handling for clay UI
// Separates input concerns from layout/rendering
var geometry = use('geometry')
var point = use('point')
var clay_input = {}
// Hit-test boxes against a mouse position
// boxes: array of box objects from clay.draw()
// mousepos: {x, y} position to test
// prev_state: previous input state for tracking changes
clay_input.hit = function hit(boxes, mousepos, prev_state = {}) {
var hovered = null
var clicked = null
// Find the topmost hovered box (iterate in reverse for proper z-order)
for (var i = boxes.length - 1; i >= 0; i--) {
var box = boxes[i]
var boundingbox = geometry.rect_move(box.boundingbox, box.config.offset)
if (geometry.rect_point_inside(boundingbox, mousepos)) {
hovered = box
break
}
}
// Update hover state
if (hovered && hovered.config.hovered) {
hovered.state = hovered.state || {}
hovered.state.hovered = true
}
// Clear previous hover state if different
if (prev_state.hovered && prev_state.hovered != hovered) {
prev_state.hovered.state = prev_state.hovered.state || {}
prev_state.hovered.state.hovered = false
}
return {
hovered: hovered,
clicked: clicked
}
}
// Handle click events
clay_input.click = function click(boxes, mousepos, button = 'left') {
var hit_result = clay_input.hit(boxes, mousepos)
var clicked = hit_result.hovered
if (clicked && clicked.config.action) {
clicked.config.action()
return clicked
}
return null
}
// Get boxes with actions for navigation
clay_input.get_actionable = function get_actionable(boxes) {
return boxes.filter(box => box.config.action)
}
return clay_input

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

@@ -1,5 +1,5 @@
var input = use('input')
var util = use('util')
return {}
var downkeys = {};
@@ -108,9 +108,9 @@ input.mouse.normal.doc = "Set the mouse to show again after hiding.";
input.keyboard = {};
input.keyboard.down = function (code) {
if (typeof code === "number") return downkeys[code];
if (typeof code === "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
return undefined;
if (typeof code == "number") return downkeys[code];
if (typeof code == "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
return null;
};
input.print_pawn_kbm = function (pawn) {
@@ -158,7 +158,7 @@ input.print_md_kbm = function print_md_kbm(pawn) {
};
input.has_bind = function (pawn, bind) {
return typeof pawn.inputs?.[bind] === "function";
return typeof pawn.inputs?.[bind] == "function";
};
input.action = {
@@ -177,17 +177,17 @@ input.tabcomplete = function tabcomplete(val, list) {
if (!val) return val;
list = filter(x => x.startsWith(val))
if (list.length === 1) {
if (list.length == 1) {
return list[0];
}
var ret = undefined;
var ret = null;
var i = val.length;
while (!ret && list.length !== 0) {
while (!ret && list.length != 0) {
var char = list[0][i];
if (
!list.every(function (x) {
return x[i] === char;
return x[i] == char;
})
)
ret = list[0].slice(0, i);
@@ -214,7 +214,7 @@ var Player = {
mouse_input(type, ...args) {
for (var pawn of [...this.pawns].reverse()) {
if (typeof pawn.inputs?.mouse?.[type] === "function") {
if (typeof pawn.inputs?.mouse?.[type] == "function") {
pawn.inputs.mouse[type].call(pawn, ...args);
pawn.inputs.post?.call(pawn);
if (!pawn.inputs.fallthru) return;
@@ -224,7 +224,7 @@ var Player = {
char_input(c) {
for (var pawn of [...this.pawns].reverse()) {
if (typeof pawn.inputs?.char === "function") {
if (typeof pawn.inputs?.char == "function") {
pawn.inputs.char.call(pawn, c);
pawn.inputs.post?.call(pawn);
if (!pawn.inputs.fallthru) return;
@@ -271,16 +271,16 @@ var Player = {
fn = inputs[cmd].released;
break;
case "down":
if (typeof inputs[cmd].down === "function") fn = inputs[cmd].down;
if (typeof inputs[cmd].down == "function") fn = inputs[cmd].down;
else if (inputs[cmd].down) fn = inputs[cmd];
}
var consumed = false;
if (typeof fn === "function") {
if (typeof fn == "function") {
fn.call(pawn, ...args);
consumed = true;
}
if (state === "released") inputs.release_post?.call(pawn);
if (state == "released") inputs.release_post?.call(pawn);
if (inputs.block) return;
if (consumed) return;
}
@@ -295,7 +295,7 @@ var Player = {
},
print_pawns() {
[...this.pawns].reverse().forEach(x => console.log(x))
[...this.pawns].reverse().forEach(x => log.console(x))
},
create() {

215
prosperon/draw2d.cm Normal file
View File

@@ -0,0 +1,215 @@
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.
`
var current_list = []
// Clear current list
draw.clear = function() {
current_list = []
}
// Get commands from current list
draw.get_commands = function() {
return current_list
}
// Helper to add a command
function add_command(type, data) {
data.cmd = type
current_list.push(data)
}
// 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.ellipse = function(pos, radii, defl, material) {
var opt = defl ? {...ellipse_def, ...defl} : 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, defl, material)
{
var opt = defl ? {...line_def, ...defl} : line_def
add_command("draw_line", {
points: points,
opt: opt,
material: material
})
}
draw.cross = function render_cross(pos, size, defl, material) {
var a = [pos.add([0, size]), pos.add([0, -size])]
var b = [pos.add([size, 0]), pos.add([-size, 0])]
draw.line(a, defl, material)
draw.line(b, defl, material)
}
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, defl, 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], defl, material)
draw.line(wing1, defl, material)
draw.line(wing2, defl, material)
}
draw.rectangle = function render_rectangle(rect, defl, material) {
var opt = defl ? {...rect_def, ...defl} : 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,
rect,
slice,
info,
material
})
}
draw.image = function image(image, rect, rotation, anchor, shear, info = {mode:"nearest"}, material = {color:{r:1,g:1,b:1,a:1}}) {
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.')
add_command("draw_image", {
image,
rect,
rotation,
anchor,
shear,
info,
material
})
}
draw.circle = function render_circle(pos, radius, defl, material) {
draw.ellipse(pos, [radius,radius], defl, material)
}
draw.text = function text(text, pos, font = 'fonts/c64.8', color = {r:1,g:1,b:1,a:1}, wrap = 0) {
add_command("draw_text", {
text,
pos,
font,
wrap,
material: {color}
})
}
draw.grid = function grid(rect, spacing, thickness = 1, offset = {x: 0, y: 0}, material) {
if (!rect || rect.x == null || rect.y == null ||
rect.width == null || rect.height == null) {
throw Error('Grid requires rect with x, y, width, height')
}
if (!spacing || typeof spacing.x == 'undefined' || typeof spacing.y == 'undefined') {
throw Error('Grid requires spacing with x and y')
}
var left = rect.x
var right = rect.x + rect.width
var top = rect.y
var bottom = rect.y + rect.height
// Apply offset and align to grid
var start_x = Math.floor((left - offset.x) / spacing.x) * spacing.x + offset.x
var end_x = Math.ceil((right - offset.x) / spacing.x) * spacing.x + offset.x
var start_y = Math.floor((top - offset.y) / spacing.y) * spacing.y + offset.y
var end_y = Math.ceil((bottom - offset.y) / spacing.y) * spacing.y + offset.y
// Draw vertical lines
for (var x = start_x; x <= end_x; x += spacing.x) {
if (x >= left && x <= right) {
var line_top = Math.max(top, start_y)
var line_bottom = Math.min(bottom, end_y)
draw.line([[x, line_top], [x, line_bottom]], {thickness: thickness}, material)
}
}
// Draw horizontal lines
for (var y = start_y; y <= end_y; y += spacing.y) {
if (y >= top && y <= bottom) {
var line_left = Math.max(left, start_x)
var line_right = Math.min(right, end_x)
draw.line([[line_left, y], [line_right, y]], {thickness: thickness}, material)
}
}
}
draw.add_command = function(cmd)
{
current_list.push(cmd)
}
return draw

124
prosperon/ease.cm Normal file
View File

@@ -0,0 +1,124 @@
var Ease = {
linear(t) {
return t
},
in(t) {
return t * t
},
out(t) {
var d = 1 - t
return 1 - d * d
},
inout(t) {
var d = -2 * t + 2
return t < 0.5 ? 2 * t * t : 1 - (d * d) / 2
},
}
function make_easing_fns(num) {
var obj = {}
obj.in = function (t) {
return Math.pow(t, num)
}
obj.out = function (t) {
return 1 - Math.pow(1 - t, num)
}
var mult = Math.pow(2, num - 1)
obj.inout = function (t) {
return t < 0.5 ? mult * Math.pow(t, num) : 1 - Math.pow(-2 * t + 2, num) / 2
}
return obj
}
Ease.quad = make_easing_fns(2)
Ease.cubic = make_easing_fns(3)
Ease.quart = make_easing_fns(4)
Ease.quint = make_easing_fns(5)
Ease.expo = {
in(t) {
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
},
out(t) {
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
},
inout(t) {
return t == 0
? 0
: t == 1
? 1
: t < 0.5
? Math.pow(2, 20 * t - 10) / 2
: (2 - Math.pow(2, -20 * t + 10)) / 2
},
}
Ease.bounce = {
in(t) {
return 1 - this.out(1 - t)
},
out(t) {
var n1 = 7.5625
var d1 = 2.75
if (t < 1 / d1) {
return n1 * t * t
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
},
inout(t) {
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
},
}
Ease.sine = {
in(t) {
return 1 - Math.cos((t * Math.PI) / 2)
},
out(t) {
return Math.sin((t * Math.PI) / 2)
},
inout(t) {
return -(Math.cos(Math.PI * t) - 1) / 2
},
}
Ease.elastic = {
in(t) {
return t == 0
? 0
: t == 1
? 1
: -Math.pow(2, 10 * t - 10) *
Math.sin((t * 10 - 10.75) * this.c4)
},
out(t) {
return t == 0
? 0
: t == 1
? 1
: Math.pow(2, -10 * t) *
Math.sin((t * 10 - 0.75) * this.c4) +
1
},
inout(t) {
return t == 0
? 0
: t == 1
? 1
: t < 0.5
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2 + 1
},
}
Ease.elastic.c4 = (2 * Math.PI) / 3
Ease.elastic.c5 = (2 * Math.PI) / 4.5
return Ease

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 input = use('controller')
var config = use('config')
var color = use('color')
var bunnyTex = graphics.texture("bunny")
@@ -65,5 +66,5 @@ this.hud = function() {
draw.images(bunnyTex, bunnies)
var msg = 'FPS: ' + fpsAvg.toFixed(2) + ' Bunnies: ' + bunnies.length
draw.text(msg, {x:0, y:0, width:config.width, height:40}, undefined, 0, Color.white, 0)
draw.text(msg, {x:0, y:0, width:config.width, height:40}, null, 0, color.white, 0)
}

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 */
var moth = use('moth')
var json = use('json')
var draw2d = use('prosperon/draw2d')
var res = 160
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');
var blob = use('blob')
/*──── import our pieces + systems ───────────────────────────────────*/
var Grid = use('grid'); // your new ctor
@@ -48,14 +41,14 @@ function updateTitle() {
break;
case 'connected':
if (myColor) {
title += (mover.turn === myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
} else {
title += mover.turn + " turn";
}
break;
}
prosperon.window.title = title
log.console(title)
}
// Initialize title
@@ -70,11 +63,11 @@ var opponentMousePos = null;
var opponentHoldingPiece = false;
var opponentSelectPos = null;
prosperon.on('mouse_button_down', function(e) {
if (e.which !== 0) return;
function handleMouseButtonDown(e) {
if (e.which != 0) return;
// Don't allow piece selection unless we have an opponent
if (gameState !== 'connected' || !opponent) return;
if (gameState != 'connected' || !opponent) return;
var mx = e.mouse.x;
var my = e.mouse.y;
@@ -83,12 +76,12 @@ prosperon.on('mouse_button_down', function(e) {
if (!grid.inBounds(c)) return;
var cell = grid.at(c);
if (cell.length && cell[0].colour === mover.turn) {
if (cell.length && cell[0].colour == mover.turn) {
selectPos = c;
holdingPiece = true;
// Send pickup notification to opponent
if (opponent) {
$_.send(opponent, {
send(opponent, {
type: 'piece_pickup',
pos: c
});
@@ -96,13 +89,13 @@ prosperon.on('mouse_button_down', function(e) {
} else {
selectPos = null;
}
})
}
prosperon.on('mouse_button_up', function(e) {
if (e.which !== 0 || !holdingPiece || !selectPos) return;
function handleMouseButtonUp(e) {
if (e.which != 0 || !holdingPiece || !selectPos) return;
// Don't allow moves unless we have an opponent and it's our turn
if (gameState !== 'connected' || !opponent || !isMyTurn) {
if (gameState != 'connected' || !opponent || !isMyTurn) {
holdingPiece = false;
return;
}
@@ -117,16 +110,16 @@ prosperon.on('mouse_button_up', function(e) {
}
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
console.log("Sending move to opponent:", opponent);
$_.send(opponent, {
log.console("Sending move to opponent:", opponent);
send(opponent, {
type: 'move',
from: selectPos,
to: c
});
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;
updateTitle();
}
@@ -135,13 +128,13 @@ prosperon.on('mouse_button_up', function(e) {
// Send piece drop notification to opponent
if (opponent) {
$_.send(opponent, {
send(opponent, {
type: 'piece_drop'
});
}
})
}
prosperon.on('mouse_motion', function(e) {
function handleMouseMotion(e) {
var mx = e.pos.x;
var my = e.pos.y;
@@ -154,15 +147,26 @@ prosperon.on('mouse_motion', function(e) {
hoverPos = c;
// Send mouse position to opponent in real-time
if (opponent && gameState === 'connected') {
$_.send(opponent, {
if (opponent && gameState == 'connected') {
send(opponent, {
type: 'mouse_move',
pos: c,
holding: holdingPiece,
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 ───────────────────────────────────────────────*/
/* ── constants ─────────────────────────────────────────────────── */
@@ -177,8 +181,8 @@ var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
function drawBoard() {
for (var y = 0; y < 8; ++y)
for (var x = 0; x < 8; ++x) {
var isMyHover = hoverPos && hoverPos[0] === x && hoverPos[1] === y;
var isOpponentHover = opponentMousePos && opponentMousePos[0] === x && opponentMousePos[1] === y;
var isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
var isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
var isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
var color = ((x+y)&1) ? dark : light;
@@ -193,7 +197,8 @@ function drawBoard() {
draw2d.rectangle(
{ x: x*S, y: y*S, width: S, height: S },
{ thickness: 0, color: color }
{ thickness: 0 },
{ color: color }
);
}
}
@@ -206,7 +211,7 @@ function isValidMoveForTurn(from, to) {
// Check if the destination has a piece of the same color
var destCell = grid.at(to);
if (destCell.length && destCell[0].colour === piece.colour) {
if (destCell.length && destCell[0].colour == piece.colour) {
return false;
}
@@ -220,22 +225,22 @@ function drawPieces() {
// Skip drawing the piece being held (by me or opponent)
if (holdingPiece && selectPos &&
piece.coord[0] === selectPos[0] &&
piece.coord[1] === selectPos[1]) {
piece.coord[0] == selectPos[0] &&
piece.coord[1] == selectPos[1]) {
return;
}
// Skip drawing the piece being held by opponent
if (opponentHoldingPiece && opponentSelectPos &&
piece.coord[0] === opponentSelectPos[0] &&
piece.coord[1] === opponentSelectPos[1]) {
piece.coord[0] == opponentSelectPos[0] &&
piece.coord[1] == opponentSelectPos[1]) {
return;
}
var r = { x: piece.coord[0]*S, y: piece.coord[1]*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
@@ -245,7 +250,7 @@ function drawPieces() {
var r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
width:S, height:S };
draw2d.image(piece.sprite, r, 0, [0,0], [0,0], {mode:"nearest"});
draw2d.image(piece.sprite, r);
}
}
@@ -257,26 +262,23 @@ function drawPieces() {
width:S, height:S };
// 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);
}
}
}
prosperon.on('draw', function() {
function update(dt)
{
return {}
}
function draw()
{
draw2d.clear()
drawBoard()
drawPieces()
})
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();
}
})
return draw2d.get_commands()
}
function startServer() {
gameState = 'server_waiting';
@@ -286,11 +288,11 @@ function startServer() {
updateTitle();
$_.portal(e => {
console.log("Portal received contact message");
log.console("Portal received contact message");
// Reply with this actor to establish connection
console.log (json.encode($_))
$_.send(e, $_);
console.log("Portal replied with server actor");
log.console (json.encode($_))
send(e, $_);
log.console("Portal replied with server actor");
}, 5678);
}
@@ -299,72 +301,61 @@ function joinServer() {
updateTitle();
function contact_fn(actor, reason) {
console.log("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
log.console("CONTACTED!", actor ? "SUCCESS" : "FAILED", reason);
if (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(opponent, {
send(opponent, {
type: 'greet',
client_actor: $_
});
} else {
console.log(`Failed to connect: ${json.encode(reason)}`);
log.console(`Failed to connect: ${json.encode(reason)}`);
gameState = 'waiting';
updateTitle();
}
}
$_.contact(contact_fn, {
address: "localhost",
address: "192.168.0.149",
port: 5678
});
}
var os = use('os')
// Set up IO actor subscription
var ioguy = {
__ACTORDATA__: {
id: os.ioactor()
}
};
$_.send(ioguy, {
type: "subscribe",
actor: $_
});
$_.receiver(e => {
if (e.type === 'game_start' || e.type === 'move' || e.type === 'greet')
console.log("Receiver got message:", e.type, e);
if (e.type === 'quit') os.exit()
if (e.kind == 'update')
send(e, update(e.dt))
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') {
console.log("Server received greet from client");
if (e.type == 'greet') {
log.console("Server received greet from client");
// Store the client's actor object for ongoing communication
opponent = e.client_actor;
console.log("Stored client actor:", json.encode(opponent));
log.console("Stored client actor:", json.encode(opponent));
gameState = 'connected';
updateTitle();
// Send game_start to the client
console.log("Sending game_start to client");
$_.send(opponent, {
log.console("Sending game_start to client");
send(opponent, {
type: 'game_start',
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') {
console.log("Game starting, I am:", e.your_color);
else if (e.type == 'game_start') {
log.console("Game starting, I am:", e.your_color);
myColor = e.your_color;
isMyTurn = (myColor === 'white');
isMyTurn = (myColor == 'white');
gameState = 'connected';
updateTitle();
} else if (e.type === 'move') {
console.log("Received move from opponent:", e.from, "to", e.to);
} else if (e.type == 'move') {
log.console("Received move from opponent:", e.from, "to", e.to);
// Apply opponent's move
var fromCell = grid.at(e.from);
if (fromCell.length) {
@@ -372,27 +363,33 @@ $_.receiver(e => {
if (mover.tryMove(piece, e.to)) {
isMyTurn = true; // It's now our turn
updateTitle();
console.log("Applied opponent move, now my turn");
log.console("Applied opponent move, now my turn");
} else {
console.log("Failed to apply opponent move");
log.console("Failed to apply opponent move");
}
} 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
opponentMousePos = e.pos;
opponentHoldingPiece = e.holding;
opponentSelectPos = e.selectPos;
} else if (e.type === 'piece_pickup') {
} else if (e.type == 'piece_pickup') {
// Opponent picked up a piece
opponentSelectPos = e.pos;
opponentHoldingPiece = true;
} else if (e.type === 'piece_drop') {
} else if (e.type == 'piece_drop') {
// Opponent dropped their piece
opponentHoldingPiece = false;
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

@@ -0,0 +1,69 @@
function grid(w, h) {
this.width = w;
this.height = h;
// create a height×width array of empty lists
this.cells = new Array(h);
for (let y = 0; y < h; y++) {
this.cells[y] = new Array(w);
for (let x = 0; x < w; x++) {
this.cells[y][x] = []; // each cell holds its own list
}
}
}
grid.prototype = {
// return the array at (x,y)
cell(x, y) {
return this.cells[y][x];
},
// alias for cell
at(pos) {
return this.cell(pos.x, pos.y);
},
// add an entity into a cell
add(entity, pos) {
this.cell(pos.x, pos.y).push(entity);
entity.coord = pos.slice();
},
// remove an entity from a cell
remove(entity, pos) {
const c = this.cell(pos.x, pos.y);
const i = c.indexOf(entity);
if (i !== -1) c.splice(i, 1);
},
// bounds check
inBounds(pos) {
return (
pos.x >= 0 && pos.x < this.width &&
pos.y >= 0 && pos.y < this.height
);
},
// call fn(entity, coord) for every entity in every cell
each(fn) {
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const list = this.cells[y][x];
for (let entity of list) {
fn(entity, entity.coord);
}
}
}
},
// printable representation
toString() {
let out = `grid [${this.width}×${this.height}]\n`;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
out += this.cells[y][x].length;
}
if (y !== this.height - 1) out += "\n";
}
return out;
}
};

View File

@@ -5,17 +5,17 @@ var MovementSystem = function(grid, rules) {
}
MovementSystem.prototype.tryMove = function (piece, to) {
if (piece.colour !== this.turn) return false;
if (piece.colour != this.turn) return false;
// normalise to into our hybrid coord
var dest = [to.x !== undefined ? to.x : to[0],
to.y !== undefined ? to.y : to[1]];
var dest = [to.x ?? t[0],
to.y ?? to[1]];
if (!this.grid.inBounds(dest)) return false;
if (!this.rules.canMove(piece, piece.coord, dest, this.grid)) return false;
var victims = this.grid.at(dest);
if (victims.length && victims[0].colour === piece.colour) return false;
if (victims.length && victims[0].colour == piece.colour) return false;
if (victims.length) victims[0].captured = true;
this.grid.remove(piece, piece.coord);
@@ -25,7 +25,7 @@ MovementSystem.prototype.tryMove = function (piece, to) {
piece.coord.x = dest.x;
piece.coord.y = dest.y;
this.turn = (this.turn === 'white') ? 'black' : 'white';
this.turn = (this.turn == 'white') ? 'black' : 'white';
return true;
};

View File

@@ -0,0 +1,45 @@
/* helper robust coord access */
function cx(c) { return c.x ?? c[0] }
function cy(c) { return c.y ?? c[1] }
/* simple move-shape checks */
var deltas = {
pawn: function (pc, dx, dy, grid, to) {
var dir = (pc.colour == 'white') ? -1 : 1;
var base = (pc.colour == 'white') ? 6 : 1;
var one = (dy == dir && dx == 0 && grid.at(to).length == 0);
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir }).length == 0 &&
grid.at(to).length == 0);
var cap = (dy == dir && Math.abs(dx) == 1 && grid.at(to).length);
return one || two || cap;
},
rook : function (pc, dx, dy) { return (dx == 0 || dy == 0); },
bishop: function (pc, dx, dy) { return Math.abs(dx) == Math.abs(dy); },
queen : function (pc, dx, dy) { return (dx == 0 || dy == 0 || Math.abs(dx) == Math.abs(dy)); },
knight: function (pc, dx, dy) { return (Math.abs(dx) == 1 && Math.abs(dy) == 2) ||
(Math.abs(dx) == 2 && Math.abs(dy) == 1); },
king : function (pc, dx, dy) { return Math.max(Math.abs(dx), Math.abs(dy)) == 1; }
};
function clearLine(from, to, grid) {
var dx = Math.sign(cx(to) - cx(from));
var dy = Math.sign(cy(to) - cy(from));
var x = cx(from) + dx, y = cy(from) + dy;
while (x != cx(to) || y != cy(to)) {
if (grid.at({ x: x, y: y }).length) return false;
x += dx; y += dy;
}
return true;
}
function canMove(piece, from, to, grid) {
var dx = cx(to) - cx(from);
var dy = cy(to) - cy(from);
var f = deltas[piece.kind];
if (!f || !f(piece, dx, dy, grid, to)) return false;
if (piece.kind == 'knight') return true;
return clearLine(from, to, grid);
}
return { canMove };

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 input = use('controller')
var config = use('config')
var color = use('color')
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 paddles
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: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 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
var msg = score1 + " " + score2
draw.text(msg, {x:0, y:10, width:config.width, height:40}, undefined, 0, Color.white, 0)
draw.text(msg, {x:0, y:10, width:config.width, height:40}, null, 0, color.white, 0)
}

View File

@@ -4,6 +4,7 @@ var render = use('render')
var graphics = use('graphics')
var input = use('input')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
@@ -35,7 +36,7 @@ function spawnApple() {
apple = {x:Math.floor(Math.random()*gridW), y:Math.floor(Math.random()*gridH)}
// Re-spawn if apple lands on snake
for (var i=0; i<snake.length; i++)
if (snake[i].x === apple.x && snake[i].y === apple.y) { spawnApple(); return }
if (snake[i].x == apple.x && snake[i].y == apple.y) { spawnApple(); return }
}
function wrap(pos) {
@@ -48,7 +49,7 @@ function wrap(pos) {
resetGame()
this.update = function(dt) {
if (gameState !== "playing") return
if (gameState != "playing") return
moveTimer += dt
if (moveTimer < moveInterval) return
moveTimer -= moveInterval
@@ -62,7 +63,7 @@ this.update = function(dt) {
// Check collision with body
for (var i=0; i<snake.length; i++) {
if (snake[i].x === head.x && snake[i].y === head.y) {
if (snake[i].x == head.x && snake[i].y == head.y) {
gameState = "gameover"
return
}
@@ -72,7 +73,7 @@ this.update = function(dt) {
snake.unshift(head)
// Eat apple?
if (head.x === apple.x && head.y === apple.y) spawnApple()
if (head.x == apple.x && head.y == apple.y) spawnApple()
else snake.pop()
}
@@ -83,15 +84,15 @@ this.hud = function() {
// Draw snake
for (var i=0; i<snake.length; 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.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."
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, undefined, 0, Color.white)
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, null, 0, color.white)
}
}
@@ -99,19 +100,19 @@ this.hud = function() {
// "Up" means y=1, so going physically up on screen
this.inputs = {
up: function() {
if (direction.y !== -1) nextDirection = {x:0,y:1}
if (direction.y != -1) nextDirection = {x:0,y:1}
},
down: function() {
if (direction.y !== 1) nextDirection = {x:0,y:-1}
if (direction.y != 1) nextDirection = {x:0,y:-1}
},
left: function() {
if (direction.x !== 1) nextDirection = {x:-1,y:0}
if (direction.x != 1) nextDirection = {x:-1,y:0}
},
right: function() {
if (direction.x !== -1) nextDirection = {x:1,y:0}
if (direction.x != -1) nextDirection = {x:1,y:0}
},
space: function() {
if (gameState==="gameover") resetGame()
if (gameState=="gameover") resetGame()
}
}

View File

@@ -0,0 +1,187 @@
// Steam Integration Example
// This example shows how to use Steam achievements and stats
var steam = use("steam");
// Achievement names (these should match your Steam app configuration)
var ACHIEVEMENTS = {
FIRST_WIN: "ACH_FIRST_WIN",
PLAY_10_GAMES: "ACH_PLAY_10_GAMES",
HIGH_SCORE: "ACH_HIGH_SCORE_1000"
};
// Stat names
var STATS = {
GAMES_PLAYED: "stat_games_played",
TOTAL_SCORE: "stat_total_score",
PLAY_TIME: "stat_play_time"
};
var steam_available = false;
var stats_loaded = false;
// Initialize Steam
function init_steam() {
if (!steam) {
log.console("Steam module not available");
return false;
}
log.console("Initializing Steam...");
steam_available = steam.steam_init();
if (steam_available) {
log.console("Steam initialized successfully");
// Request current stats/achievements
if (steam.stats.stats_request()) {
log.console("Stats requested");
stats_loaded = true;
}
} else {
log.console("Failed to initialize Steam");
}
return steam_available;
}
// Update Steam (call this regularly, e.g., once per frame)
function update_steam() {
if (steam_available) {
steam.steam_run_callbacks();
}
}
// Unlock an achievement
function unlock_achievement(achievement_name) {
if (!steam_available || !stats_loaded) return false;
// Check if already unlocked
var unlocked = steam.achievement.achievement_get(achievement_name);
if (unlocked) {
log.console("Achievement already unlocked:", achievement_name);
return true;
}
// Unlock it
if (steam.achievement.achievement_set(achievement_name)) {
log.console("Achievement unlocked:", achievement_name);
// Store stats to make it permanent
steam.stats.stats_store();
return true;
}
return false;
}
// Update a stat
function update_stat(stat_name, value, is_float) {
if (!steam_available || !stats_loaded) return false;
var success;
if (is_float) {
success = steam.stats.stats_set_float(stat_name, value);
} else {
success = steam.stats.stats_set_int(stat_name, value);
}
if (success) {
log.console("Stat updated:", stat_name, "=", value);
steam.stats.stats_store();
}
return success;
}
// Get a stat value
function get_stat(stat_name, is_float) {
if (!steam_available || !stats_loaded) return 0;
if (is_float) {
return steam.stats.stats_get_float(stat_name) || 0;
} else {
return steam.stats.stats_get_int(stat_name) || 0;
}
}
// Example game logic
var games_played = 0;
var total_score = 0;
var current_score = 0;
function start_game() {
games_played = get_stat(STATS.GAMES_PLAYED, false);
total_score = get_stat(STATS.TOTAL_SCORE, false);
current_score = 0;
log.console("Starting game #" + (games_played + 1));
}
function end_game(score) {
current_score = score;
games_played++;
total_score += score;
// Update stats
update_stat(STATS.GAMES_PLAYED, games_played, false);
update_stat(STATS.TOTAL_SCORE, total_score, false);
// Check for achievements
if (games_played == 1) {
unlock_achievement(ACHIEVEMENTS.FIRST_WIN);
}
if (games_played >= 10) {
unlock_achievement(ACHIEVEMENTS.PLAY_10_GAMES);
}
if (score >= 1000) {
unlock_achievement(ACHIEVEMENTS.HIGH_SCORE);
}
}
// Cloud save example
function save_to_cloud(save_data) {
if (!steam_available) return false;
var json_data = JSON.stringify(save_data);
return steam.cloud.cloud_write("savegame.json", json_data);
}
function load_from_cloud() {
if (!steam_available) return null;
var data = steam.cloud.cloud_read("savegame.json");
if (data) {
// Convert ArrayBuffer to string
var decoder = new TextDecoder();
var json_str = decoder.decode(data);
return JSON.parse(json_str);
}
return null;
}
// Cleanup
function cleanup_steam() {
if (steam_available) {
steam.steam_shutdown();
log.console("Steam shut down");
}
}
// Export the API
module.exports = {
init: init_steam,
update: update_steam,
cleanup: cleanup_steam,
unlock_achievement: unlock_achievement,
update_stat: update_stat,
get_stat: get_stat,
start_game: start_game,
end_game: end_game,
save_to_cloud: save_to_cloud,
load_from_cloud: load_from_cloud,
is_available: function() { return steam_available; }
};

View File

@@ -1,6 +1,7 @@
var draw = use('draw2d')
var input = use('input')
var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0]
@@ -127,10 +128,10 @@ function clearLines() {
}
}
// Score
if (lines===1) score += 100
else if (lines===2) score += 300
else if (lines===3) score += 500
else if (lines===4) score += 800
if (lines==1) score += 100
else if (lines==2) score += 300
else if (lines==3) score += 500
else if (lines==4) score += 800
linesCleared += lines
level = Math.floor(linesCleared/10)
}
@@ -152,7 +153,7 @@ spawnPiece()
this.update = function(dt) {
if (gameOver) return
// ========== Horizontal Movement Gate ==========
// ======= Horizontal Movement Gate =======
var leftPressed = input.keyboard.down('a')
var rightPressed = input.keyboard.down('d')
var horizontalMove = 0
@@ -190,7 +191,7 @@ this.update = function(dt) {
hMoveTimer -= dt
prevLeft = leftPressed
prevRight = rightPressed
// ========== End Horizontal Movement Gate ==========
// ======= End Horizontal Movement Gate =======
// Rotate with W (once per press, no spinning)
if (input.keyboard.down('w')) {
@@ -248,7 +249,7 @@ this.hud = function() {
}
// Next piece window
draw.text("Next", {x:70, y:5, width:50, height:10}, undefined, 0, Color.white)
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
if (nextPiece) {
for (var i=0; i<nextPiece.blocks.length; i++) {
var nx = nextPiece.blocks[i][0]
@@ -261,10 +262,10 @@ this.hud = function() {
// Score & Level
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
draw.text(info, {x:70, y:30, width:90, height:50}, undefined, 0, Color.white)
draw.text(info, {x:70, y:30, width:90, height:50}, null, 0, color.white)
if (gameOver) {
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, undefined, 0, Color.red)
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, null, 0, color.red)
}
}

View File

@@ -1,5 +1,5 @@
var geometry = this
geometry[prosperon.DOC] = `
geometry[cell.DOC] = `
A collection of geometry-related functions for circles, spheres, boxes, polygons,
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')
geometry.box = {}
geometry.box[prosperon.DOC] = `
geometry.box[cell.DOC] = `
An object for box-related operations. Overridden later by a function definition, so
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).
@@ -16,7 +16,7 @@ its direct usage is overshadowed. Contains:
geometry.box.points = function (ll, ur) {
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 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].
@@ -24,14 +24,14 @@ Compute the four corners of a box given lower-left and upper-right corners.
`
geometry.sphere = {}
geometry.sphere[prosperon.DOC] = `
geometry.sphere[cell.DOC] = `
Sphere-related geometry functions:
- volume(r): Return the volume of a sphere with radius r.
- random(r, theta, phi): Return a random point on or inside a sphere.
`
geometry.circle = {}
geometry.circle[prosperon.DOC] = `
geometry.circle[cell.DOC] = `
Circle-related geometry functions:
- 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.
@@ -40,22 +40,22 @@ Circle-related geometry functions:
geometry.sphere.volume = function (r) {
return (Math.pi * r * r * r * 4) / 3
}
geometry.sphere.volume[prosperon.DOC] = `
geometry.sphere.volume[cell.DOC] = `
:param r: The sphere radius.
:return: The volume of the sphere, calculated as (4/3) * pi * r^3.
`
geometry.sphere.random = function (r, theta = [0, 1], phi = [-0.5, 0.5]) {
if (typeof r === "number") r = [r, r]
if (typeof theta === "number") theta = [theta, theta]
if (typeof phi === "number") phi = [phi, phi]
if (typeof r == "number") r = [r, r]
if (typeof theta == "number") theta = [theta, theta]
if (typeof phi == "number") phi = [phi, phi]
var ra = Math.random_range(r[0], r[1])
var ta = Math.turn2rad(Math.random_range(theta[0], theta[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)]
}
geometry.sphere.random[prosperon.DOC] = `
geometry.sphere.random[cell.DOC] = `
: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 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) {
return Math.pi * r * r
}
geometry.circle.area[prosperon.DOC] = `
geometry.circle.area[cell.DOC] = `
:param r: Radius of the circle.
:return: The area, pi * r^2.
`
@@ -74,7 +74,7 @@ geometry.circle.area[prosperon.DOC] = `
geometry.circle.random = function (r, theta) {
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 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.
@@ -91,7 +91,7 @@ geometry.box = function (w, h) {
]
return points
}
geometry.box[prosperon.DOC] = `
geometry.box[cell.DOC] = `
:param w: The width 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].
@@ -101,7 +101,7 @@ Construct a box centered at the origin with the given width and height. This ove
geometry.ngon = function (radius, 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 n: Number of sides/vertices.
: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))
return points
}
geometry.arc[prosperon.DOC] = `
geometry.arc[cell.DOC] = `
: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 n: Number of segments (if <=1, empty array is returned).
@@ -131,7 +131,7 @@ geometry.circle.points = function (radius, n) {
if (n <= 1) return []
return geometry.arc(radius, 360, n)
}
geometry.circle.points[prosperon.DOC] = `
geometry.circle.points[cell.DOC] = `
:param radius: The circle's radius.
:param n: Number of points around the 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) {
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 ur: Upper-right 2D coordinate (relative offset in x,y).
: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) })
}
geometry.sortpointsccw[prosperon.DOC] = `
geometry.sortpointsccw[cell.DOC] = `
:param points: An array of 2D points.
: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.
@@ -185,61 +185,61 @@ geometry.points2cm = function(points) {
})
return [x / n, y / n]
}
geometry.points2cm[prosperon.DOC] = `
geometry.points2cm[cell.DOC] = `
:param points: An array of 2D 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 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 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 b: Rectangle {x,y,w,h}.
: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 b: Rectangle {x,y,w,h}.
:return: A new rectangle that covers the bounds of both input rectangles.
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 outer: A rectangle that may contain 'inner'.
: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}.
: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 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.
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 point: A 2D point [px, py].
: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}.
: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 offset: A 2D vector to add to the rectangle's position.
:return: A new rectangle with updated x,y offset.

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

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