fix nota
This commit is contained in:
68
AGENTS.md
68
AGENTS.md
@@ -1,26 +1,52 @@
|
|||||||
# AGENTS.md
|
# Agent Development Guide
|
||||||
|
|
||||||
## Project Overview
|
## Build Commands
|
||||||
This is a game engine developed using a QuickJS fork as its scripting language. It is an actor based system, based on Douglas Crockford's Misty. It is a Meson compiled project with a number of dependencies.
|
- **Debug build**: `make debug` or `meson setup build_dbg -Dbuildtype=debugoptimized && meson compile -C build_dbg`
|
||||||
|
- **Release build**: `make release` or `meson setup -Dbuildtype=release -Db_lto=true build_release && meson compile -C build_release`
|
||||||
|
- **Fast build**: `make fast` or `meson setup build_fast && meson compile -C build_fast`
|
||||||
|
- **Web build**: `make web` or `meson setup -Deditor=false -Dbuildtype=minsize build_web && meson compile -C build_web`
|
||||||
|
- **Cross-platform**: `make crosswin` (Windows), `make dockerlinux` (Linux), `make dockeremc` (Emscripten)
|
||||||
|
|
||||||
## File Structure
|
## Test Commands
|
||||||
- `source/`: Contains the C source code
|
- **Run all tests**: `meson test -C build_dbg`
|
||||||
- `scripts/`: Contains script code that is loaded on executable start, and modules
|
- **Run single test**: `./cell tests/<test_name>` (e.g., `./cell tests/send`, `./cell tests/empty`)
|
||||||
- `shaders/`: Contains shaders that ship with the engine (for shader based backends)
|
- **Available tests**: spawn_actor, empty, nota, wota, portalspawner, overling, send, delay
|
||||||
- `benchmarks/`: Benchmark programs for testing speed
|
- **Test examples**: `./cell examples/nat`, `./cell examples/http_download_actor`
|
||||||
- `tests/`: Unit tests
|
|
||||||
- `examples/`: Contains full game examples
|
|
||||||
|
|
||||||
## Coding Practices
|
## Code Style Guidelines
|
||||||
- Use K&R style C
|
|
||||||
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
|
|
||||||
|
|
||||||
## Instructions
|
### C Code Style (K&R)
|
||||||
- When generating code, adhere to the coding practices outlined above.
|
- **Indentation**: 4 spaces, no tabs
|
||||||
- When adding new features, ensure they align with the project's goals.
|
- **Naming**: `snake_case` for identifiers
|
||||||
- When fixing bugs, review the code carefully before making changes.
|
- **Files**: Small, focused C files with header guards required
|
||||||
- When writing unit tests, cover all important scenarios.
|
- **Formatting**: Use `.clang-format` (GNU style, 2-space continuation, attach braces)
|
||||||
|
- **Comments**: Focus on "why", keep lines to ~100 columns
|
||||||
|
|
||||||
## Compiling, running, and testing
|
### Cell Language Style (JavaScript-like)
|
||||||
- To compile the code, run "make", which generates a prosperon executable in build_dbg/, and copy it into the root folder
|
- **Indentation**: 2 spaces
|
||||||
- Run a test by giving it as its command: so ./prosperon tests/overling.js would run the test overling.js, ./prosperon tests/nota.js runs the nota benchmark
|
- **Declarations**: `def` for constants, `var` for block-scoped variables (like `let`)
|
||||||
|
- **Equality**: Use `==` only (strict equality, no coercion)
|
||||||
|
- **Null checks**: Use `== null` (no `undefined` in Cell)
|
||||||
|
- **Imports**: Use `use('path')` for modules (not ES6 import/export)
|
||||||
|
- **Modules**: `*.cm` files must return values, `*.ce` files are programs (don't return)
|
||||||
|
- **Patterns**: Prefer objects, prototypes, closures over classes
|
||||||
|
- **Formatting**: Use `.prettierrc` (semicolons, double quotes, trailing commas, 1000 char width)
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
- **Modules**: `*.cm` (return objects, get frozen with `stone()`)
|
||||||
|
- **Programs**: `*.ce` (execute top-to-bottom, register handlers)
|
||||||
|
- **Naming**: Lowercase paths with `/` separators
|
||||||
|
- **Resolution**: Engine appends `.cm`/`.ce` automatically when probing
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Use `log.console()` and `log.error()` for logging
|
||||||
|
- Actor system handles message timeouts automatically
|
||||||
|
- Check for circular imports (detected and reported)
|
||||||
|
- Validate module returns before freezing
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- Keep modules small and composable
|
||||||
|
- Use actor messaging for communication (no shared objects)
|
||||||
|
- Follow hierarchical actor system (overlings/underlings)
|
||||||
|
- Prefer functional programming patterns
|
||||||
|
- Document with `cell.DOC` system
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
nav:
|
nav:
|
||||||
- index.md
|
- index.md
|
||||||
|
- quickstart.md
|
||||||
- tutorial.md
|
- tutorial.md
|
||||||
- actors.md
|
- actors.md
|
||||||
- rendering.md
|
- rendering.md
|
||||||
@@ -9,4 +10,4 @@ nav:
|
|||||||
- ...
|
- ...
|
||||||
- Appendix A - dull: dull
|
- Appendix A - dull: dull
|
||||||
- Appendix B - api: api
|
- Appendix B - api: api
|
||||||
|
|
||||||
|
|||||||
133
docs/quickstart.md
Normal file
133
docs/quickstart.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Quickstart
|
||||||
|
|
||||||
|
This quickstart walks through running Cell programs, exploring examples, and creating your first module and program. It assumes the `./cell` executable is present at the repo root (already built or downloaded).
|
||||||
|
|
||||||
|
## 1) Initialize a Project Shop (.cell)
|
||||||
|
|
||||||
|
Create the project structure used for module management and bytecode cache:
|
||||||
|
|
||||||
|
- `./cell init`
|
||||||
|
|
||||||
|
This creates `.cell/` with `cell.toml`, `lock.toml`, `modules/`, `build/`, and `patches/`.
|
||||||
|
|
||||||
|
## 2) Run Built‑in Examples
|
||||||
|
|
||||||
|
Examples under `examples/` are programs (`*.ce`) you can run directly:
|
||||||
|
|
||||||
|
- NAT portal server (introduction service):
|
||||||
|
- `./cell examples/nat`
|
||||||
|
- NAT client (contacts a portal):
|
||||||
|
- `./cell examples/nat_client`
|
||||||
|
- Non‑blocking HTTP download actor:
|
||||||
|
- `./cell examples/http_download_actor`
|
||||||
|
|
||||||
|
Tip: Use these as references for portals, contacts, and non‑blocking I/O.
|
||||||
|
|
||||||
|
## 3) Run the Accio Game
|
||||||
|
|
||||||
|
Accio lives under `accio/`. The program is `accio/accio.ce` and supports modes via arguments:
|
||||||
|
|
||||||
|
- Start menu: `./cell accio start`
|
||||||
|
- Load a level: `./cell accio level game/1.json`
|
||||||
|
- Analyze assets: `./cell accio analyze`
|
||||||
|
- Clean level data: `./cell accio clean`
|
||||||
|
|
||||||
|
Arguments after the program path are available inside the program via `arg` (e.g., `arg[0] == 'start'`).
|
||||||
|
|
||||||
|
## 4) Your First Module (.cm) and Program (.ce)
|
||||||
|
|
||||||
|
Create a module that returns a frozen API object, and a program that uses it.
|
||||||
|
|
||||||
|
- File: `examples/hello.cm` (module)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Returns a value (frozen by the engine)
|
||||||
|
return {
|
||||||
|
greet: function(name) {
|
||||||
|
return `Hello, ${name}!`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- File: `examples/hello.ce` (program)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var hello = use('examples/hello')
|
||||||
|
|
||||||
|
log.console(hello.greet('Cell'))
|
||||||
|
|
||||||
|
$_.receiver(function(msg) {
|
||||||
|
if (msg.type == 'ping') {
|
||||||
|
send(msg, {type:'pong'})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$_.delay(_ => $_.stop(), 0.1)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
|
||||||
|
- `./cell examples/hello`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Modules are `*.cm` and must return a value. The engine deep‑freezes return values, so mutate via new objects or closures rather than in‑place.
|
||||||
|
- Programs are `*.ce` and must not return a value. They run top‑to‑bottom when spawned and can register handlers via `$_.receiver()` and schedule work via `$_.delay()` or `$_.clock()`.
|
||||||
|
|
||||||
|
## 5) Spawning Child Programs (Actors)
|
||||||
|
|
||||||
|
Programs can spawn other programs and receive lifecycle events.
|
||||||
|
|
||||||
|
- File: `examples/spawner.ce`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
$_.receiver(function(e) {
|
||||||
|
if (e.type == 'greet' && e.actor) {
|
||||||
|
log.console('Child greeted me')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$_.start(function(info) {
|
||||||
|
if (info.type == 'greet') {
|
||||||
|
log.console('Spawned child actor')
|
||||||
|
}
|
||||||
|
}, 'examples/hello.ce')
|
||||||
|
|
||||||
|
$_.delay(_ => $_.stop(), 0.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
|
||||||
|
- `./cell examples/spawner`
|
||||||
|
|
||||||
|
## 6) Module Shop Basics
|
||||||
|
|
||||||
|
The module shop manages vendored dependencies under `.cell/modules/` and caches compiled bytecode under `.cell/build/`.
|
||||||
|
|
||||||
|
Common commands (all are programs under `scripts/`):
|
||||||
|
|
||||||
|
- Initialize (if you haven’t already):
|
||||||
|
- `./cell init`
|
||||||
|
- See available commands:
|
||||||
|
- `./cell help`
|
||||||
|
- Configure system/actor settings:
|
||||||
|
- `./cell config list`
|
||||||
|
- `./cell config set system.reply_timeout 60`
|
||||||
|
- `./cell config actor prosperon/_sdl_video set resolution 1280x720`
|
||||||
|
- Download or vendor dependencies (from `.cell/cell.toml`):
|
||||||
|
- `./cell mod download`
|
||||||
|
|
||||||
|
## 7) How Module Resolution Works
|
||||||
|
|
||||||
|
- `use('path')` checks:
|
||||||
|
1) The current module’s directory for `path.cm` while loading.
|
||||||
|
2) The mounted roots (the program’s directory is mounted) for `path.cm`.
|
||||||
|
3) Embedded/native modules if no script file is found.
|
||||||
|
- Modules compile to `.cell/build/<canonical-path>.o` and reuse the cache if newer than the source.
|
||||||
|
- Scripted modules can extend embedded modules via prototype; if the script returns nothing, the embedded module is used as‑is.
|
||||||
|
|
||||||
|
## 8) Next Steps
|
||||||
|
|
||||||
|
- Language details: `docs/cell.md`
|
||||||
|
- Actors, programs, and messaging: `docs/actors.md`
|
||||||
|
- Rendering, input, and resources: `docs/rendering.md`, `docs/input.md`, `docs/resources.md`
|
||||||
|
- Full API reference: `docs/api/`
|
||||||
655
prosperon/_sdl_gpu.cm
Normal file
655
prosperon/_sdl_gpu.cm
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
var render = {}
|
||||||
|
|
||||||
|
var io = use('io')
|
||||||
|
var controller = use('controller')
|
||||||
|
var tracy = use('tracy')
|
||||||
|
var graphics = use('graphics')
|
||||||
|
var imgui = use('imgui')
|
||||||
|
var transform = use('transform')
|
||||||
|
var color = use('color')
|
||||||
|
|
||||||
|
var base_pipeline = {
|
||||||
|
vertex: "sprite.vert",
|
||||||
|
fragment: "sprite.frag",
|
||||||
|
primitive: "triangle", // point, line, linestrip, triangle, trianglestrip
|
||||||
|
fill: true, // false for lines
|
||||||
|
depth: {
|
||||||
|
compare: "greater_equal", // never/less/equal/less_equal/greater/not_equal/greater_equal/always
|
||||||
|
test: false,
|
||||||
|
write: false,
|
||||||
|
bias: 0,
|
||||||
|
bias_slope_scale: 0,
|
||||||
|
bias_clamp: 0
|
||||||
|
},
|
||||||
|
stencil: {
|
||||||
|
enabled: true,
|
||||||
|
front: {
|
||||||
|
compare: "equal", // never/less/equal/less_equal/greater/neq/greq/always
|
||||||
|
fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap
|
||||||
|
depth_fail: "keep",
|
||||||
|
pass: "keep"
|
||||||
|
},
|
||||||
|
back: {
|
||||||
|
compare: "equal", // never/less/equal/less_equal/greater/neq/greq/always
|
||||||
|
fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap
|
||||||
|
depth_fail: "keep",
|
||||||
|
pass: "keep"
|
||||||
|
},
|
||||||
|
test: true,
|
||||||
|
compare_mask: 0,
|
||||||
|
write_mask: 0
|
||||||
|
},
|
||||||
|
blend: {
|
||||||
|
enabled: false,
|
||||||
|
src_rgb: "zero", // zero/one/src_color/one_minus_src_color/dst_color/one_minus_dst_color/src_alpha/one_minus_src_alpha/dst_alpha/one_minus_dst_alpha/constant_color/one_minus_constant_color/src_alpha_saturate
|
||||||
|
dst_rgb: "zero",
|
||||||
|
op_rgb: "add", // add/sub/rev_sub/min/max
|
||||||
|
src_alpha: "one",
|
||||||
|
dst_alpha: "zero",
|
||||||
|
op_alpha: "add"
|
||||||
|
},
|
||||||
|
cull: "none", // none/front/back
|
||||||
|
face: "cw", // cw/ccw
|
||||||
|
alpha_to_coverage: false,
|
||||||
|
multisample: {
|
||||||
|
count: 1, // number of multisamples
|
||||||
|
mask: 0xFFFFFFFF,
|
||||||
|
domask: false
|
||||||
|
},
|
||||||
|
label: "scripted pipeline",
|
||||||
|
target: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sprite_pipeline = Object.create(base_pipeline);
|
||||||
|
sprite_pipeline.blend = {
|
||||||
|
enabled:true,
|
||||||
|
src_rgb: "src_alpha",
|
||||||
|
dst_rgb: "one_minus_src_alpha",
|
||||||
|
op_rgb: "add", // add/sub/rev_sub/min/max
|
||||||
|
src_alpha: "one",
|
||||||
|
dst_alpha: "zero",
|
||||||
|
op_alpha: "add"
|
||||||
|
};
|
||||||
|
|
||||||
|
var context;
|
||||||
|
|
||||||
|
sprite_pipeline.target = {
|
||||||
|
color_targets: [{
|
||||||
|
format:"rgba8",
|
||||||
|
blend:sprite_pipeline.blend
|
||||||
|
}],
|
||||||
|
depth: "d32 float s8"
|
||||||
|
};
|
||||||
|
|
||||||
|
var driver = "vulkan"
|
||||||
|
switch(os.platform()) {
|
||||||
|
case "Linux":
|
||||||
|
driver = "vulkan"
|
||||||
|
break
|
||||||
|
case "Windows":
|
||||||
|
// driver = "direct3d12"
|
||||||
|
driver = "vulkan"
|
||||||
|
break
|
||||||
|
case "macOS":
|
||||||
|
driver = "metal"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit_transform = new transform;
|
||||||
|
|
||||||
|
var cur = {};
|
||||||
|
cur.images = [];
|
||||||
|
cur.samplers = [];
|
||||||
|
|
||||||
|
var tbuffer;
|
||||||
|
function full_upload(buffers) {
|
||||||
|
var cmds = context.acquire_cmd_buffer();
|
||||||
|
tbuffer = context.upload(cmds, buffers, tbuffer);
|
||||||
|
cmds.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_pipeline(pass, pipeline) {
|
||||||
|
make_pipeline(pipeline)
|
||||||
|
pass.bind_pipeline(pipeline.gpu)
|
||||||
|
pass.pipeline = pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
var main_pass;
|
||||||
|
|
||||||
|
var cornflower = [62/255,96/255,113/255,1];
|
||||||
|
|
||||||
|
function get_pipeline_ubo_slot(pipeline, name) {
|
||||||
|
if (!pipeline.vertex.reflection.ubos) return;
|
||||||
|
for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) {
|
||||||
|
var ubo = pipeline.vertex.reflection.ubos[i];
|
||||||
|
if (ubo.name.endsWith(name))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function transpose4x4(val) {
|
||||||
|
var out = [];
|
||||||
|
out[0] = val[0]; out[1] = val[4]; out[2] = val[8]; out[3] = val[12];
|
||||||
|
out[4] = val[1]; out[5] = val[5]; out[6] = val[9]; out[7] = val[13];
|
||||||
|
out[8] = val[2]; out[9] = val[6]; out[10] = val[10];out[11] = val[14];
|
||||||
|
out[12] = val[3];out[13] = val[7];out[14] = val[11];out[15] = val[15];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ubo_obj_to_array(pipeline, name, obj) {
|
||||||
|
var ubo;
|
||||||
|
for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) {
|
||||||
|
ubo = pipeline.vertex.reflection.ubos[i];
|
||||||
|
if (ubo.name.endsWith(name)) break;
|
||||||
|
}
|
||||||
|
var type = pipeline.vertex.reflection.types[ubo.type];
|
||||||
|
var len = 0;
|
||||||
|
for (var mem of type.members)
|
||||||
|
len += type_to_byte_count(mem.type);
|
||||||
|
|
||||||
|
var buf = new ArrayBuffer(len);
|
||||||
|
var view = new DataView(buf);
|
||||||
|
|
||||||
|
for (var mem of type.members) {
|
||||||
|
var val = obj[mem.name];
|
||||||
|
if (!val) throw new Error (`Could not find ${mem.name} on supplied object`);
|
||||||
|
|
||||||
|
if (mem.name == 'model')
|
||||||
|
val = transpose4x4(val.array());
|
||||||
|
|
||||||
|
for (var i = 0; i < val.length; i++)
|
||||||
|
view.setFloat32(mem.offset + i*4, val[i],true);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function type_to_byte_count(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'float': return 4;
|
||||||
|
case 'vec2': return 8;
|
||||||
|
case 'vec3': return 12;
|
||||||
|
case 'vec4': return 16;
|
||||||
|
case 'mat4': return 64;
|
||||||
|
default: throw new Error("Unknown or unsupported float-based type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sprite_model_ubo = {
|
||||||
|
model: unit_transform,
|
||||||
|
color: [1,1,1,1]
|
||||||
|
};
|
||||||
|
|
||||||
|
var shader_cache = {};
|
||||||
|
var shader_times = {};
|
||||||
|
|
||||||
|
function make_pipeline(pipeline) {
|
||||||
|
if (pipeline.hasOwnProperty("gpu")) return; // this pipeline has already been made
|
||||||
|
|
||||||
|
if (typeof pipeline.vertex == 'string')
|
||||||
|
pipeline.vertex = make_shader(pipeline.vertex);
|
||||||
|
if (typeof pipeline.fragment == 'string')
|
||||||
|
pipeline.fragment = make_shader(pipeline.fragment)
|
||||||
|
|
||||||
|
// 1) Reflection data for vertex shader
|
||||||
|
var refl = pipeline.vertex.reflection
|
||||||
|
if (!refl || !refl.inputs || !Array.isArray(refl.inputs)) {
|
||||||
|
pipeline.gpu = context.make_pipeline(pipeline);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputs = refl.inputs
|
||||||
|
var buffer_descriptions = []
|
||||||
|
var attributes = []
|
||||||
|
|
||||||
|
// 2) Build buffer + attribute for each reflection input
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
var inp = inputs[i]
|
||||||
|
var typeStr = inp.type
|
||||||
|
var nameStr = (inp.name || "").toUpperCase()
|
||||||
|
var pitch = 4
|
||||||
|
var fmt = "float1"
|
||||||
|
|
||||||
|
if (typeStr == "vec2") {
|
||||||
|
pitch = 8
|
||||||
|
fmt = "float2"
|
||||||
|
} else if (typeStr == "vec3") {
|
||||||
|
pitch = 12
|
||||||
|
fmt = "float3"
|
||||||
|
} else if (typeStr == "vec4") {
|
||||||
|
if (nameStr.indexOf("COLOR") >= 0) {
|
||||||
|
pitch = 16
|
||||||
|
fmt = "color"
|
||||||
|
} else {
|
||||||
|
pitch = 16
|
||||||
|
fmt = "float4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_descriptions.push({
|
||||||
|
slot: i,
|
||||||
|
pitch: pitch,
|
||||||
|
input_rate: "vertex",
|
||||||
|
instance_step_rate: 0,
|
||||||
|
name:inp.name.split(".").pop()
|
||||||
|
})
|
||||||
|
|
||||||
|
attributes.push({
|
||||||
|
location: inp.location,
|
||||||
|
buffer_slot: i,
|
||||||
|
format: fmt,
|
||||||
|
offset: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.vertex_buffer_descriptions = buffer_descriptions
|
||||||
|
pipeline.vertex_attributes = attributes
|
||||||
|
|
||||||
|
pipeline.gpu = context.make_pipeline(pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
var shader_type;
|
||||||
|
|
||||||
|
function make_shader(sh_file) {
|
||||||
|
var file = `shaders/${shader_type}/${sh_file}.${shader_type}`
|
||||||
|
if (shader_cache[file]) return shader_cache[file]
|
||||||
|
var refl = json.decode(io.slurp(`shaders/reflection/${sh_file}.json`))
|
||||||
|
|
||||||
|
var shader = {
|
||||||
|
code: io.slurpbytes(file),
|
||||||
|
format: shader_type,
|
||||||
|
stage: sh_file.endsWith("vert") ? "vertex" : "fragment",
|
||||||
|
num_samplers: refl.separate_samplers ? refl.separate_samplers.length : 0,
|
||||||
|
num_textures: 0,
|
||||||
|
num_storage_buffers: refl.separate_storage_buffers ? refl.separate_storage_buffers.length : 0,
|
||||||
|
num_uniform_buffers: refl.ubos ? refl.ubos.length : 0,
|
||||||
|
entrypoint: shader_type == "msl" ? "main0" : "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
shader.gpu = context.make_shader(shader)
|
||||||
|
shader.reflection = refl;
|
||||||
|
shader_cache[file] = shader
|
||||||
|
shader.file = sh_file
|
||||||
|
return shader
|
||||||
|
}
|
||||||
|
|
||||||
|
var render_queue = [];
|
||||||
|
var hud_queue = [];
|
||||||
|
|
||||||
|
var current_queue = render_queue;
|
||||||
|
|
||||||
|
var std_sampler = {
|
||||||
|
min_filter: "nearest",
|
||||||
|
mag_filter: "nearest",
|
||||||
|
mipmap: "linear",
|
||||||
|
u: "repeat",
|
||||||
|
v: "repeat",
|
||||||
|
w: "repeat",
|
||||||
|
mip_bias: 0,
|
||||||
|
max_anisotropy: 0,
|
||||||
|
compare_op: "none",
|
||||||
|
min_lod: 0,
|
||||||
|
max_lod: 0,
|
||||||
|
anisotropy: false,
|
||||||
|
compare: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function upload_model(model) {
|
||||||
|
var bufs = [];
|
||||||
|
for (var i in model) {
|
||||||
|
if (typeof model[i] != 'object') continue;
|
||||||
|
bufs.push(model[i]);
|
||||||
|
}
|
||||||
|
context.upload(this, bufs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_model(pass, pipeline, model) {
|
||||||
|
var buffers = pipeline.vertex_buffer_descriptions;
|
||||||
|
var bufs = [];
|
||||||
|
if (buffers)
|
||||||
|
for (var b of buffers) {
|
||||||
|
if (b.name in model) bufs.push(model[b.name])
|
||||||
|
else throw Error (`could not find buffer ${b.name} on model`);
|
||||||
|
}
|
||||||
|
pass.bind_buffers(0,bufs);
|
||||||
|
pass.bind_index_buffer(model.indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind_mat(pass, pipeline, mat) {
|
||||||
|
var imgs = [];
|
||||||
|
var refl = pipeline.fragment.reflection;
|
||||||
|
if (refl.separate_images) {
|
||||||
|
for (var i of refl.separate_images) {
|
||||||
|
if (i.name in mat) {
|
||||||
|
var tex = mat[i.name];
|
||||||
|
imgs.push({texture:tex.texture, sampler:tex.sampler});
|
||||||
|
} else
|
||||||
|
throw Error (`could not find all necessary images: ${i.name}`)
|
||||||
|
}
|
||||||
|
pass.bind_samplers(false, 0,imgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function group_sprites_by_texture(sprites, mesh) {
|
||||||
|
if (sprites.length == 0) return;
|
||||||
|
for (var i = 0; i < sprites.length; i++) {
|
||||||
|
sprites[i].mesh = mesh;
|
||||||
|
sprites[i].first_index = i*6;
|
||||||
|
sprites[i].num_indices = 6;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
// The code below is an alternate approach to grouping by image. Currently not in use.
|
||||||
|
/*
|
||||||
|
var groups = [];
|
||||||
|
var group = {image:sprites[0].image, first_index:0};
|
||||||
|
var count = 1;
|
||||||
|
for (var i = 1; i < sprites.length; i++) {
|
||||||
|
if (sprites[i].image == group.image) {
|
||||||
|
count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
group.num_indices = count*6;
|
||||||
|
var newgroup = {image:sprites[i].image, first_index:group.first_index+group.num_indices};
|
||||||
|
group = newgroup;
|
||||||
|
groups.push(group);
|
||||||
|
count=1;
|
||||||
|
}
|
||||||
|
group.num_indices = count*6;
|
||||||
|
return groups;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
var main_color = {
|
||||||
|
type:"2d",
|
||||||
|
format: "rgba8",
|
||||||
|
layers: 1,
|
||||||
|
mip_levels: 1,
|
||||||
|
samples: 0,
|
||||||
|
sampler:true,
|
||||||
|
color_target:true
|
||||||
|
};
|
||||||
|
|
||||||
|
var main_depth = {
|
||||||
|
type: "2d",
|
||||||
|
format: "d32 float s8",
|
||||||
|
layers:1,
|
||||||
|
mip_levels:1,
|
||||||
|
samples:0,
|
||||||
|
sampler:true,
|
||||||
|
depth_target:true
|
||||||
|
};
|
||||||
|
|
||||||
|
function render_camera(cmds, camera) {
|
||||||
|
var pass;
|
||||||
|
delete camera.target // TODO: HORRIBLE
|
||||||
|
if (!camera.target) {
|
||||||
|
main_color.width = main_depth.width = camera.size.x;
|
||||||
|
main_color.height = main_depth.height = camera.size.y;
|
||||||
|
camera.target = {
|
||||||
|
color_targets: [{
|
||||||
|
texture: context.texture(main_color),
|
||||||
|
mip_level:0,
|
||||||
|
layer: 0,
|
||||||
|
load:"clear",
|
||||||
|
store:"store",
|
||||||
|
clear: cornflower
|
||||||
|
}],
|
||||||
|
depth_stencil: {
|
||||||
|
texture: context.texture(main_depth),
|
||||||
|
clear:1,
|
||||||
|
load:"dont_care",
|
||||||
|
store:"dont_care",
|
||||||
|
stencil_load:"dont_care",
|
||||||
|
stencil_store:"dont_care",
|
||||||
|
stencil_clear:0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffers = [];
|
||||||
|
buffers = buffers.concat(graphics.queue_sprite_mesh(render_queue));
|
||||||
|
var unique_meshes = [...new Set(render_queue.map(x => x.mesh))];
|
||||||
|
for (var q of unique_meshes)
|
||||||
|
buffers = buffers.concat([q.pos, q.color, q.uv, q.indices]);
|
||||||
|
|
||||||
|
buffers = buffers.concat(graphics.queue_sprite_mesh(hud_queue));
|
||||||
|
for (var q of hud_queue)
|
||||||
|
if (q.type == 'geometry') buffers = buffers.concat([q.mesh.pos, q.mesh.color, q.mesh.uv, q.mesh.indices]);
|
||||||
|
|
||||||
|
full_upload(buffers)
|
||||||
|
|
||||||
|
var pass = cmds.render_pass(camera.target);
|
||||||
|
|
||||||
|
var pipeline = sprite_pipeline;
|
||||||
|
bind_pipeline(pass,pipeline);
|
||||||
|
|
||||||
|
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
||||||
|
if (camslot != null)
|
||||||
|
cmds.camera(camera, camslot);
|
||||||
|
|
||||||
|
modelslot = get_pipeline_ubo_slot(pipeline, "model");
|
||||||
|
if (modelslot != null) {
|
||||||
|
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo);
|
||||||
|
cmds.push_vertex_uniform_data(modelslot, ubo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mesh;
|
||||||
|
var img;
|
||||||
|
var modelslot;
|
||||||
|
|
||||||
|
cmds.push_debug_group("draw")
|
||||||
|
for (var group of render_queue) {
|
||||||
|
if (mesh != group.mesh) {
|
||||||
|
mesh = group.mesh;
|
||||||
|
bind_model(pass,pipeline,mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.image && img != group.image) {
|
||||||
|
img = group.image;
|
||||||
|
img.sampler = std_sampler;
|
||||||
|
bind_mat(pass,pipeline,{diffuse:img});
|
||||||
|
}
|
||||||
|
|
||||||
|
pass.draw_indexed(group.num_indices, 1, group.first_index, 0, 0);
|
||||||
|
}
|
||||||
|
cmds.pop_debug_group()
|
||||||
|
|
||||||
|
cmds.push_debug_group("hud")
|
||||||
|
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
|
||||||
|
if (camslot != null)
|
||||||
|
cmds.hud(camera.size, camslot);
|
||||||
|
|
||||||
|
for (var group of hud_queue) {
|
||||||
|
if (mesh != group.mesh) {
|
||||||
|
mesh = group.mesh;
|
||||||
|
bind_model(pass,pipeline,mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.image && img != group.image) {
|
||||||
|
img = group.image;
|
||||||
|
img.sampler = std_sampler;
|
||||||
|
bind_mat(pass,pipeline,{diffuse:img});
|
||||||
|
}
|
||||||
|
|
||||||
|
pass.draw_indexed(group.num_indices, 1, group.first_index, 0, 0);
|
||||||
|
}
|
||||||
|
cmds.pop_debug_group();
|
||||||
|
|
||||||
|
pass?.end();
|
||||||
|
|
||||||
|
render_queue = [];
|
||||||
|
hud_queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var swaps = [];
|
||||||
|
render.present = function() {
|
||||||
|
os.clean_transforms();
|
||||||
|
var cmds = context.acquire_cmd_buffer();
|
||||||
|
render_camera(cmds, prosperon.camera);
|
||||||
|
var swapchain_tex = cmds.acquire_swapchain();
|
||||||
|
if (!swapchain_tex)
|
||||||
|
cmds.cancel();
|
||||||
|
else {
|
||||||
|
var torect = prosperon.camera.draw_rect(prosperon.window.size);
|
||||||
|
torect.texture = swapchain_tex;
|
||||||
|
if (swapchain_tex) {
|
||||||
|
cmds.blit({
|
||||||
|
src: prosperon.camera.target.color_targets[0].texture,
|
||||||
|
dst: torect,
|
||||||
|
filter:"nearest",
|
||||||
|
load: "clear"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (imgui) { // draws any imgui commands present
|
||||||
|
cmds.push_debug_group("imgui")
|
||||||
|
imgui.prepend(cmds);
|
||||||
|
var pass = cmds.render_pass({
|
||||||
|
color_targets:[{texture:swapchain_tex}]});
|
||||||
|
imgui.endframe(cmds,pass);
|
||||||
|
pass.end();
|
||||||
|
cmds.pop_debug_group()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmds.submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stencil_write = {
|
||||||
|
compare: "always",
|
||||||
|
fail_op: "replace",
|
||||||
|
depth_fail_op: "replace",
|
||||||
|
pass_op: "replace"
|
||||||
|
};
|
||||||
|
|
||||||
|
function stencil_writer(ref) {
|
||||||
|
var pipe = Object.create(base_pipeline);
|
||||||
|
Object.assign(pipe, {
|
||||||
|
stencil: {
|
||||||
|
enabled: true,
|
||||||
|
front: stencil_write,
|
||||||
|
back: stencil_write,
|
||||||
|
write:true,
|
||||||
|
read:true,
|
||||||
|
ref:ref
|
||||||
|
},
|
||||||
|
write_mask: colormask.none
|
||||||
|
});
|
||||||
|
return pipe;
|
||||||
|
}.hashify();
|
||||||
|
|
||||||
|
// objects by default draw where the stencil buffer is 0
|
||||||
|
function fillmask(ref) {
|
||||||
|
var pipe = stencil_writer(ref);
|
||||||
|
render.use_shader('screenfill.cg', pipe);
|
||||||
|
render.draw(shape.quad);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stencil_invert = {
|
||||||
|
compare: "always",
|
||||||
|
fail_op: "invert",
|
||||||
|
depth_fail_op: "invert",
|
||||||
|
pass_op: "invert"
|
||||||
|
};
|
||||||
|
|
||||||
|
function mask(image, pos, scale, rotation = 0, ref = 1) {
|
||||||
|
if (typeof image == 'string')
|
||||||
|
image = graphics.texture(image);
|
||||||
|
|
||||||
|
var tex = image.texture;
|
||||||
|
if (scale) scale = scale.div([tex.width,tex.height]);
|
||||||
|
else scale = [1,1,1]
|
||||||
|
|
||||||
|
var pipe = stencil_writer(ref);
|
||||||
|
render.use_shader('sprite.cg', pipe);
|
||||||
|
var t = new transform;
|
||||||
|
t.trs(pos, null, scale);
|
||||||
|
set_model(t);
|
||||||
|
render.use_mat({
|
||||||
|
diffuse:image.texture,
|
||||||
|
rect: image.rect,
|
||||||
|
shade: color.white
|
||||||
|
});
|
||||||
|
render.draw(shape.quad);
|
||||||
|
}
|
||||||
|
|
||||||
|
render.viewport = function(rect) {
|
||||||
|
context.viewport(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
render.scissor = function(rect) {
|
||||||
|
render.viewport(rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
var std_sampler
|
||||||
|
|
||||||
|
if (tracy) tracy.gpu_init()
|
||||||
|
|
||||||
|
render.queue = function(cmd) {
|
||||||
|
if (Array.isArray(cmd))
|
||||||
|
for (var i of cmd) current_queue.push(i)
|
||||||
|
else
|
||||||
|
current_queue.push(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
render.setup_draw = function() {
|
||||||
|
current_queue = render_queue;
|
||||||
|
prosperon.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
render.setup_hud = function() {
|
||||||
|
current_queue = hud_queue;
|
||||||
|
prosperon.hud();
|
||||||
|
}
|
||||||
|
|
||||||
|
render.initialize = function(config)
|
||||||
|
{
|
||||||
|
var default_conf = {
|
||||||
|
title:`Prosperon [${prosperon.version}-${prosperon.revision}]`,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
icon: graphics.make_texture(io.slurpbytes('icons/moon.gif')),
|
||||||
|
high_dpi:0,
|
||||||
|
alpha:1,
|
||||||
|
fullscreen:0,
|
||||||
|
sample_count:1,
|
||||||
|
enable_clipboard:true,
|
||||||
|
enable_dragndrop: true,
|
||||||
|
max_dropped_files: 1,
|
||||||
|
swap_interval: 1,
|
||||||
|
name: "Prosperon",
|
||||||
|
version:prosperon.version + "-" + prosperon.revision,
|
||||||
|
identifier: "world.pockle.prosperon",
|
||||||
|
creator: "Pockle World LLC",
|
||||||
|
copyright: "Copyright Pockle World 2025",
|
||||||
|
type: "game",
|
||||||
|
url: "https://prosperon.dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.__proto__ = default_conf
|
||||||
|
|
||||||
|
prosperon.camera = use('ext/camera').make()
|
||||||
|
prosperon.camera.size = [config.width,config.height]
|
||||||
|
|
||||||
|
prosperon.window = prosperon.engine_start(config)
|
||||||
|
|
||||||
|
context = prosperon.window.make_gpu(false,driver)
|
||||||
|
context.window = prosperon.window
|
||||||
|
context.claim_window(prosperon.window)
|
||||||
|
context.set_swapchain('sdr', 'vsync')
|
||||||
|
|
||||||
|
if (imgui) imgui.init(context, prosperon.window)
|
||||||
|
|
||||||
|
shader_type = context.shader_format()[0];
|
||||||
|
|
||||||
|
std_sampler = context.make_sampler({
|
||||||
|
min_filter: "nearest",
|
||||||
|
mag_filter: "nearest",
|
||||||
|
mipmap_mode: "nearest",
|
||||||
|
address_mode_u: "repeat",
|
||||||
|
address_mode_v: "repeat",
|
||||||
|
address_mode_w: "repeat"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return render
|
||||||
|
|
||||||
502
prosperon/prosperon_sdl_renderer.cm
Normal file
502
prosperon/prosperon_sdl_renderer.cm
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
var prosperon = {}
|
||||||
|
|
||||||
|
// This file is hard coded for the SDL renderer case
|
||||||
|
|
||||||
|
var video = use('sdl_video')
|
||||||
|
var imgui = use('imgui')
|
||||||
|
var surface = use('surface')
|
||||||
|
|
||||||
|
var default_window = {
|
||||||
|
// Basic properties
|
||||||
|
title: "Prosperon Window",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
|
||||||
|
// Position - can be numbers or "centered"
|
||||||
|
x: null, // SDL_WINDOWPOS_null by default
|
||||||
|
y: null, // SDL_WINDOWPOS_null by default
|
||||||
|
|
||||||
|
// Window behavior flags
|
||||||
|
resizable: true,
|
||||||
|
fullscreen: false,
|
||||||
|
hidden: false,
|
||||||
|
borderless: false,
|
||||||
|
alwaysOnTop: false,
|
||||||
|
minimized: false,
|
||||||
|
maximized: false,
|
||||||
|
|
||||||
|
// Input grabbing
|
||||||
|
mouseGrabbed: false,
|
||||||
|
keyboardGrabbed: false,
|
||||||
|
|
||||||
|
// Display properties
|
||||||
|
highPixelDensity: false,
|
||||||
|
transparent: false,
|
||||||
|
opacity: 1.0, // 0.0 to 1.0
|
||||||
|
|
||||||
|
// Focus behavior
|
||||||
|
notFocusable: false,
|
||||||
|
|
||||||
|
// Special window types (mutually exclusive)
|
||||||
|
utility: false, // Utility window (not in taskbar)
|
||||||
|
tooltip: false, // Tooltip window (requires parent)
|
||||||
|
popupMenu: false, // Popup menu window (requires parent)
|
||||||
|
|
||||||
|
// Graphics API flags (let SDL choose if not specified)
|
||||||
|
opengl: false, // Force OpenGL context
|
||||||
|
vulkan: false, // Force Vulkan context
|
||||||
|
metal: false, // Force Metal context (macOS)
|
||||||
|
|
||||||
|
// Advanced properties
|
||||||
|
parent: null, // Parent window for tooltips/popups/modal
|
||||||
|
modal: false, // Modal to parent window (requires parent)
|
||||||
|
externalGraphicsContext: false, // Use external graphics context
|
||||||
|
|
||||||
|
// Input handling
|
||||||
|
textInput: true, // Enable text input on creation
|
||||||
|
}
|
||||||
|
|
||||||
|
var win_config = arg[0] || {}
|
||||||
|
win_config.__proto__ = default_window
|
||||||
|
|
||||||
|
var window = new video.window(win_config)
|
||||||
|
var renderer = window.make_renderer()
|
||||||
|
|
||||||
|
// Initialize ImGui with the window and renderer
|
||||||
|
imgui.init(window, renderer)
|
||||||
|
imgui.newframe()
|
||||||
|
|
||||||
|
var os = use('os');
|
||||||
|
var io = use('io');
|
||||||
|
var rasterize = use('rasterize');
|
||||||
|
var time = use('time')
|
||||||
|
var tilemap = use('tilemap')
|
||||||
|
var geometry = use('geometry')
|
||||||
|
var res = use('resources')
|
||||||
|
var input = use('input')
|
||||||
|
|
||||||
|
var graphics = use('graphics')
|
||||||
|
|
||||||
|
var camera = {}
|
||||||
|
|
||||||
|
function updateCameraMatrix(cam) {
|
||||||
|
def win_w = logical.width
|
||||||
|
def win_h = logical.height
|
||||||
|
def view_w = (cam.size?.[0] ?? win_w) / cam.zoom
|
||||||
|
def view_h = (cam.size?.[1] ?? win_h) / cam.zoom
|
||||||
|
|
||||||
|
def ox = cam.pos[0] - view_w * (cam.anchor?.[0] ?? 0)
|
||||||
|
def oy = cam.pos[1] - view_h * (cam.anchor?.[1] ?? 0)
|
||||||
|
|
||||||
|
def vx = (cam.viewport?.x ?? 0) * win_w
|
||||||
|
def vy = (cam.viewport?.y ?? 0) * win_h
|
||||||
|
def vw = (cam.viewport?.width ?? 1) * win_w
|
||||||
|
def vh = (cam.viewport?.height ?? 1) * win_h
|
||||||
|
|
||||||
|
def sx = vw / view_w
|
||||||
|
def sy = vh / view_h // flip-Y later
|
||||||
|
|
||||||
|
/* affine matrix that SDL wants (Y going down) */
|
||||||
|
cam.a = sx
|
||||||
|
cam.c = vx - sx * ox
|
||||||
|
cam.e = -sy // <-- minus = flip Y
|
||||||
|
cam.f = vy + vh + sy * oy
|
||||||
|
|
||||||
|
/* convenience inverses */
|
||||||
|
cam.ia = 1 / cam.a
|
||||||
|
cam.ic = -cam.c / cam.a
|
||||||
|
cam.ie = 1 / cam.e
|
||||||
|
cam.if = -cam.f / cam.e
|
||||||
|
|
||||||
|
camera = cam
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- forward transform ----
|
||||||
|
function worldToScreenPoint([x,y], camera) {
|
||||||
|
return {
|
||||||
|
x: camera.a * x + camera.c,
|
||||||
|
y: camera.e * y + camera.f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- inverse transform ----
|
||||||
|
function screenToWorldPoint(pos, camera) {
|
||||||
|
return {
|
||||||
|
x: camera.ia * pos[0] + camera.ic,
|
||||||
|
y: camera.ie * pos[1] + camera.if
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- rectangle (two corner) ----
|
||||||
|
function worldToScreenRect({x,y,width,height}, camera) {
|
||||||
|
// map bottom-left and top-right
|
||||||
|
def x1 = camera.a * x + camera.c;
|
||||||
|
def y1 = camera.e * y + camera.f;
|
||||||
|
def x2 = camera.a * (x + width) + camera.c;
|
||||||
|
def y2 = camera.e * (y + height) + camera.f;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x:Math.min(x1,x2),
|
||||||
|
y:Math.min(y1,y2),
|
||||||
|
width:Math.abs(x2-x1),
|
||||||
|
height:Math.abs(y2-y1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameactor
|
||||||
|
|
||||||
|
var images = {}
|
||||||
|
|
||||||
|
var renderer_commands = []
|
||||||
|
|
||||||
|
var win_size = {width:500,height:500}
|
||||||
|
var logical = {width:500,height:500}
|
||||||
|
|
||||||
|
function get_img_gpu(img)
|
||||||
|
{
|
||||||
|
if (img.gpu) return img.gpu
|
||||||
|
var surf = new surface(img.cpu)
|
||||||
|
img.gpu = renderer.load_texture(surf)
|
||||||
|
return img.gpu
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert high-level draw commands to low-level renderer commands
|
||||||
|
function translate_draw_commands(commands) {
|
||||||
|
renderer_commands.length = 0
|
||||||
|
|
||||||
|
commands.forEach(function(cmd) {
|
||||||
|
if (cmd.material && cmd.material.color && typeof cmd.material.color == 'object') {
|
||||||
|
renderer.drawColor = cmd.material.color
|
||||||
|
}
|
||||||
|
switch(cmd.cmd) {
|
||||||
|
case "camera":
|
||||||
|
updateCameraMatrix(cmd.camera, win_size.width, win_size.height)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_rect":
|
||||||
|
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||||
|
renderer.fillRect(cmd.rect)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_line":
|
||||||
|
var points = cmd.points.map(p => {
|
||||||
|
var pt = worldToScreenPoint(p, camera)
|
||||||
|
return[pt.x, pt.y]
|
||||||
|
})
|
||||||
|
renderer.line(points)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_point":
|
||||||
|
cmd.pos = worldToScreenPoint(cmd.pos, camera)
|
||||||
|
renderer.point(cmd.pos)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_image":
|
||||||
|
var img = graphics.texture(cmd.image)
|
||||||
|
var gpu = get_img_gpu(img)
|
||||||
|
|
||||||
|
if (!cmd.scale) cmd.scale = {x:1,y:1}
|
||||||
|
cmd.rect.width ??= img.width
|
||||||
|
cmd.rect.height ??= img.height
|
||||||
|
cmd.rect.width = cmd.rect.width * cmd.scale.x
|
||||||
|
cmd.rect.height = cmd.rect.height * cmd.scale.y
|
||||||
|
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||||
|
renderer.texture(
|
||||||
|
gpu,
|
||||||
|
img.rect,
|
||||||
|
cmd.rect,
|
||||||
|
0,
|
||||||
|
{x:0,y:0}
|
||||||
|
)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_slice9":
|
||||||
|
var img = graphics.texture(cmd.image)
|
||||||
|
var gpu = get_img_gpu(img)
|
||||||
|
if (!gpu) break
|
||||||
|
var rect = worldToScreenRect(cmd.rect, camera)
|
||||||
|
renderer.texture9Grid(
|
||||||
|
gpu,
|
||||||
|
img.rect,
|
||||||
|
cmd.slice,
|
||||||
|
cmd.slice,
|
||||||
|
cmd.slice,
|
||||||
|
cmd.slice,
|
||||||
|
1,
|
||||||
|
rect
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "draw_text":
|
||||||
|
if (!cmd.text) break
|
||||||
|
if (!cmd.pos) break
|
||||||
|
|
||||||
|
// Get font from the font string (e.g., "smalle.16")
|
||||||
|
var font = graphics.get_font(cmd.font)
|
||||||
|
if (!font.gpu) {
|
||||||
|
var surf = new surface(font.surface)
|
||||||
|
font.gpu = renderer.load_texture(surf)
|
||||||
|
}
|
||||||
|
var gpu = font.gpu
|
||||||
|
|
||||||
|
// Create text geometry buffer
|
||||||
|
var text_mesh = graphics.make_text_buffer(
|
||||||
|
cmd.text,
|
||||||
|
{x: cmd.pos.x, y: cmd.pos.y},
|
||||||
|
[cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a],
|
||||||
|
cmd.wrap || 0,
|
||||||
|
font
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!text_mesh) break
|
||||||
|
|
||||||
|
if (text_mesh.xy.length == 0) break
|
||||||
|
|
||||||
|
// Transform XY coordinates using camera matrix
|
||||||
|
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||||
|
var transformed_xy = geometry.transform_xy_blob(text_mesh.xy, camera_params)
|
||||||
|
|
||||||
|
// Create transformed geometry object
|
||||||
|
var geom = {
|
||||||
|
xy: transformed_xy,
|
||||||
|
xy_stride: text_mesh.xy_stride,
|
||||||
|
uv: text_mesh.uv,
|
||||||
|
uv_stride: text_mesh.uv_stride,
|
||||||
|
color: text_mesh.color,
|
||||||
|
color_stride: text_mesh.color_stride,
|
||||||
|
indices: text_mesh.indices,
|
||||||
|
num_vertices: text_mesh.num_vertices,
|
||||||
|
num_indices: text_mesh.num_indices,
|
||||||
|
size_indices: text_mesh.size_indices
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.geometry_raw(gpu, geom.xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "tilemap":
|
||||||
|
var geometryCommands = cmd.tilemap.draw()
|
||||||
|
|
||||||
|
for (var geomCmd of geometryCommands) {
|
||||||
|
var img = graphics.texture(geomCmd.image)
|
||||||
|
if (!img) continue
|
||||||
|
|
||||||
|
if (!img.gpu) {
|
||||||
|
var surf = new surface(img.cpu)
|
||||||
|
img.gpu = renderer.load_texture(surf)
|
||||||
|
}
|
||||||
|
var gpu = img.gpu
|
||||||
|
|
||||||
|
var geom = geomCmd.geometry
|
||||||
|
|
||||||
|
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||||
|
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
|
||||||
|
|
||||||
|
renderer.geometry_raw(gpu, transformed_xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case "geometry":
|
||||||
|
var gpu = get_img_gpu(cmd.image)
|
||||||
|
log.console(json.encode(cmd))
|
||||||
|
var geom = cmd.geometry
|
||||||
|
|
||||||
|
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||||
|
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
|
||||||
|
|
||||||
|
renderer.geometry_raw(gpu, transformed_xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return renderer_commands
|
||||||
|
}
|
||||||
|
|
||||||
|
///// input /////
|
||||||
|
var input_cb
|
||||||
|
var input_rate = 1/60
|
||||||
|
function poll_input() {
|
||||||
|
var evs = input.get_events()
|
||||||
|
|
||||||
|
// Filter and transform events
|
||||||
|
if (renderer && Array.isArray(evs)) {
|
||||||
|
var filteredEvents = []
|
||||||
|
var wantMouse = imgui.wantmouse()
|
||||||
|
var wantKeys = imgui.wantkeys()
|
||||||
|
|
||||||
|
for (var i = 0; i < evs.length; i++) {
|
||||||
|
var event = evs[i]
|
||||||
|
var shouldInclude = true
|
||||||
|
|
||||||
|
// Filter mouse events if ImGui wants mouse input
|
||||||
|
if (wantMouse && (event.type == 'mouse_motion' ||
|
||||||
|
event.type == 'mouse_button_down' ||
|
||||||
|
event.type == 'mouse_button_up' ||
|
||||||
|
event.type == 'mouse_wheel')) {
|
||||||
|
shouldInclude = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter keyboard events if ImGui wants keyboard input
|
||||||
|
if (wantKeys && (event.type == 'key_down' ||
|
||||||
|
event.type == 'key_up' ||
|
||||||
|
event.type == 'text_input' ||
|
||||||
|
event.type == 'text_editing')) {
|
||||||
|
shouldInclude = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldInclude) {
|
||||||
|
// Transform mouse coordinates from window to renderer coordinates
|
||||||
|
if (event.pos && (event.type == 'mouse_motion' ||
|
||||||
|
event.type == 'mouse_button_down' ||
|
||||||
|
event.type == 'mouse_button_up' ||
|
||||||
|
event.type == 'mouse_wheel')) {
|
||||||
|
// Convert window coordinates to renderer logical coordinates
|
||||||
|
var logicalPos = renderer.coordsFromWindow(event.pos)
|
||||||
|
event.pos = logicalPos
|
||||||
|
}
|
||||||
|
// Handle drop events which also have position
|
||||||
|
if (event.pos && (event.type == 'drop_file' ||
|
||||||
|
event.type == 'drop_text' ||
|
||||||
|
event.type == 'drop_position')) {
|
||||||
|
var logicalPos = renderer.coordsFromWindow(event.pos)
|
||||||
|
event.pos = logicalPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle window events
|
||||||
|
if (event.type == 'window_pixel_size_changed') {
|
||||||
|
win_size.width = event.width
|
||||||
|
win_size.height = event.height
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == 'quit')
|
||||||
|
$_.stop()
|
||||||
|
|
||||||
|
if (event.type.includes('key')) {
|
||||||
|
if (event.key)
|
||||||
|
event.key = input.keyname(event.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type.startsWith('mouse_') && event.pos && event.pos.y)
|
||||||
|
event.pos.y = -event.pos.y + logical.height
|
||||||
|
|
||||||
|
filteredEvents.push(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evs = filteredEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
input_cb(evs)
|
||||||
|
$_.delay(poll_input, input_rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
prosperon.input = function(fn)
|
||||||
|
{
|
||||||
|
input_cb = fn
|
||||||
|
poll_input()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) helper to build & send a batch, then call done()
|
||||||
|
prosperon.create_batch = function create_batch(draw_cmds, done) {
|
||||||
|
renderer.drawColor = {r:0.1,g:0.1,b:0.15,a:1}
|
||||||
|
renderer.clear()
|
||||||
|
|
||||||
|
if (draw_cmds && draw_cmds.length)
|
||||||
|
var commands = translate_draw_commands(draw_cmds)
|
||||||
|
|
||||||
|
renderer.drawColor = {r:1,g:1,b:1,a:1}
|
||||||
|
imgui.endframe(renderer)
|
||||||
|
imgui.newframe()
|
||||||
|
|
||||||
|
renderer.present()
|
||||||
|
|
||||||
|
if (done) done()
|
||||||
|
}
|
||||||
|
|
||||||
|
////////// dmon hot reload ////////
|
||||||
|
function poll_file_changes() {
|
||||||
|
dmon.poll(e => {
|
||||||
|
if (e.action == 'modify' || e.action == 'create') {
|
||||||
|
// Check if it's an image file
|
||||||
|
var ext = e.file.split('.').pop().toLowerCase()
|
||||||
|
var imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tga', 'webp', 'qoi', 'ase', 'aseprite']
|
||||||
|
|
||||||
|
if (imageExts.includes(ext)) {
|
||||||
|
// Try to find the full path for this image
|
||||||
|
var possiblePaths = [
|
||||||
|
e.file,
|
||||||
|
e.root + e.file,
|
||||||
|
res.find_image(e.file.split('/').pop().split('.')[0])
|
||||||
|
].filter(p => p)
|
||||||
|
|
||||||
|
for (var path of possiblePaths) {
|
||||||
|
graphics.tex_hotreload(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Schedule next poll in 0.5 seconds
|
||||||
|
$_.delay(poll_file_changes, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dmon = use('dmon')
|
||||||
|
prosperon.dmon = function()
|
||||||
|
{
|
||||||
|
dmon.watch('.')
|
||||||
|
poll_file_changes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var window_cmds = {
|
||||||
|
size(size) {
|
||||||
|
window.size = size
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
prosperon.set_window = function(config)
|
||||||
|
{
|
||||||
|
for (var c in config)
|
||||||
|
if (window_cmds[c]) window_cmds[c](config[c])
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderer_cmds = {
|
||||||
|
resolution(e) {
|
||||||
|
logical.width = e.width
|
||||||
|
logical.height = e.height
|
||||||
|
renderer.logicalPresentation = {...e}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prosperon.set_renderer = function(config)
|
||||||
|
{
|
||||||
|
for (var c in config)
|
||||||
|
if (renderer_cmds[c]) renderer_cmds[c](config[c])
|
||||||
|
}
|
||||||
|
|
||||||
|
prosperon.init = function() {
|
||||||
|
// No longer needed since we initialize directly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to load textures directly to the renderer
|
||||||
|
prosperon.load_texture = function(surface_data) {
|
||||||
|
var surf = new surface(surface_data)
|
||||||
|
if (!surf) return null
|
||||||
|
|
||||||
|
var tex = renderer.load_texture(surf)
|
||||||
|
if (!tex) return null
|
||||||
|
|
||||||
|
// Set pixel mode to nearest for all textures
|
||||||
|
tex.scaleMode = "nearest"
|
||||||
|
|
||||||
|
var tex_id = allocate_id()
|
||||||
|
resources.texture[tex_id] = tex
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: tex_id,
|
||||||
|
texture: tex,
|
||||||
|
width: tex.width,
|
||||||
|
height: tex.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prosperon
|
||||||
@@ -276,14 +276,9 @@ graphics.texture = function texture(path) {
|
|||||||
height: Math.floor(img_32k.height / 8),
|
height: Math.floor(img_32k.height / 8),
|
||||||
mode: 'linear'
|
mode: 'linear'
|
||||||
})
|
})
|
||||||
log.console(img_32k.pixels.length)
|
|
||||||
log.console(surf_4k.pixels.length)
|
|
||||||
log.console(json.encode(surf_4k))
|
|
||||||
|
|
||||||
var qoi_data = surface.compress_qoi(surf_4k)
|
var qoi_data = qoi.encode(surf_4k)
|
||||||
|
|
||||||
log.console(json.encode(qoi_data))
|
|
||||||
|
|
||||||
if (qoi_data && qoi_data.pixels) {
|
if (qoi_data && qoi_data.pixels) {
|
||||||
if (!io.exists(cache_dir)) {
|
if (!io.exists(cache_dir)) {
|
||||||
io.mkdir(cache_dir)
|
io.mkdir(cache_dir)
|
||||||
|
|||||||
198
source/nota.h
198
source/nota.h
@@ -5,7 +5,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "kim.h"
|
#include "kim.h"
|
||||||
|
|
||||||
/* Nota type nibble values */
|
|
||||||
#define NOTA_BLOB 0x00
|
#define NOTA_BLOB 0x00
|
||||||
#define NOTA_TEXT 0x10
|
#define NOTA_TEXT 0x10
|
||||||
#define NOTA_ARR 0x20
|
#define NOTA_ARR 0x20
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
#define NOTA_PRIVATE 0x08
|
#define NOTA_PRIVATE 0x08
|
||||||
#define NOTA_SYSTEM 0x09
|
#define NOTA_SYSTEM 0x09
|
||||||
|
|
||||||
/* Some internal constants/macros (used in varint logic, etc.) */
|
|
||||||
#define NOTA_CONT 0x80
|
#define NOTA_CONT 0x80
|
||||||
#define NOTA_DATA 0x7f
|
#define NOTA_DATA 0x7f
|
||||||
#define NOTA_INT_DATA 0x07
|
#define NOTA_INT_DATA 0x07
|
||||||
@@ -33,7 +31,6 @@
|
|||||||
#define CONTINUE(CHAR) (CHAR>>7)
|
#define CONTINUE(CHAR) (CHAR>>7)
|
||||||
#define UTF8_DATA 0x3f
|
#define UTF8_DATA 0x3f
|
||||||
|
|
||||||
/* A helper to get the high-level Nota type nibble from a byte */
|
|
||||||
static inline int nota_type(const char *nota) { return (*nota) & 0x70; }
|
static inline int nota_type(const char *nota) { return (*nota) & 0x70; }
|
||||||
|
|
||||||
char *nota_read_blob(long long *len, char **blob, char *nota);
|
char *nota_read_blob(long long *len, char **blob, char *nota);
|
||||||
@@ -50,10 +47,8 @@ typedef struct NotaBuffer {
|
|||||||
size_t capacity; /* allocated size of data */
|
size_t capacity; /* allocated size of data */
|
||||||
} NotaBuffer;
|
} NotaBuffer;
|
||||||
|
|
||||||
/* Initialize a NotaBuffer with a given initial capacity. */
|
|
||||||
void nota_buffer_init(NotaBuffer *nb, size_t initial_capacity);
|
void nota_buffer_init(NotaBuffer *nb, size_t initial_capacity);
|
||||||
|
|
||||||
/* Free the buffer's internal memory. (Does NOT free nb itself.) */
|
|
||||||
void nota_buffer_free(NotaBuffer *nb);
|
void nota_buffer_free(NotaBuffer *nb);
|
||||||
|
|
||||||
void nota_write_blob (NotaBuffer *nb, unsigned long long nbits, const char *data);
|
void nota_write_blob (NotaBuffer *nb, unsigned long long nbits, const char *data);
|
||||||
@@ -73,25 +68,18 @@ void nota_write_sym (NotaBuffer *nb, int sym);
|
|||||||
|
|
||||||
#include "kim.h"
|
#include "kim.h"
|
||||||
|
|
||||||
/* -------------------------------------------------------
|
|
||||||
HELPER: skip a varint
|
|
||||||
------------------------------------------------------- */
|
|
||||||
static inline char *nota_skip(char *nota)
|
static inline char *nota_skip(char *nota)
|
||||||
{
|
{
|
||||||
while (CONTINUE(*nota)) {
|
while (CONTINUE(*nota))
|
||||||
nota++;
|
nota++;
|
||||||
}
|
|
||||||
return nota + 1;
|
return nota + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------
|
|
||||||
HELPER: read a varint
|
|
||||||
------------------------------------------------------- */
|
|
||||||
char *nota_read_num(long long *n, char *nota)
|
char *nota_read_num(long long *n, char *nota)
|
||||||
{
|
{
|
||||||
if (!n) {
|
if (!n)
|
||||||
return nota_skip(nota);
|
return nota_skip(nota);
|
||||||
}
|
|
||||||
unsigned char b = (unsigned char)*nota;
|
unsigned char b = (unsigned char)*nota;
|
||||||
long long result = b & NOTA_HEAD_DATA;
|
long long result = b & NOTA_HEAD_DATA;
|
||||||
nota++;
|
nota++;
|
||||||
@@ -106,7 +94,7 @@ char *nota_read_num(long long *n, char *nota)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Count how many bits of varint we need to encode n,
|
/* Count how many bits of varint we need to encode n,
|
||||||
with sb “special bits” in the first byte. */
|
with sb “special bits” in the first byte */
|
||||||
static inline int nota_bits(long long n, int sb)
|
static inline int nota_bits(long long n, int sb)
|
||||||
{
|
{
|
||||||
if (n == 0) return sb;
|
if (n == 0) return sb;
|
||||||
@@ -116,7 +104,6 @@ static inline int nota_bits(long long n, int sb)
|
|||||||
return needed;
|
return needed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write a varint into *nota, with sb bits in the first char (which is already set). */
|
|
||||||
static inline char *nota_continue_num(long long n, char *nota, int sb)
|
static inline char *nota_continue_num(long long n, char *nota, int sb)
|
||||||
{
|
{
|
||||||
int bits = nota_bits(n, sb);
|
int bits = nota_bits(n, sb);
|
||||||
@@ -272,7 +259,6 @@ void nota_buffer_free(NotaBuffer *nb)
|
|||||||
nb->capacity = 0;
|
nb->capacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate 'len' bytes in the buffer and return a pointer to them. */
|
|
||||||
static char *nota_buffer_alloc(NotaBuffer *nb, size_t len)
|
static char *nota_buffer_alloc(NotaBuffer *nb, size_t len)
|
||||||
{
|
{
|
||||||
nota_buffer_grow(nb, len);
|
nota_buffer_grow(nb, len);
|
||||||
@@ -363,183 +349,11 @@ void nota_write_record(NotaBuffer *nb, unsigned long long count)
|
|||||||
nb->size -= (10 - used);
|
nb->size -= (10 - used);
|
||||||
}
|
}
|
||||||
|
|
||||||
void nota_write_number_str(NotaBuffer *nb, const char *str)
|
|
||||||
{
|
|
||||||
/* -------------------------------------------
|
|
||||||
1) Parse sign
|
|
||||||
------------------------------------------- */
|
|
||||||
int negative = 0;
|
|
||||||
if (*str == '+') {
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
else if (*str == '-') {
|
|
||||||
negative = 1;
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
2) Parse integer part
|
|
||||||
------------------------------------------- */
|
|
||||||
long long coefficient = 0;
|
|
||||||
int got_digits = 0;
|
|
||||||
|
|
||||||
while (*str >= '0' && *str <= '9') {
|
|
||||||
got_digits = 1;
|
|
||||||
int d = (*str - '0');
|
|
||||||
str++;
|
|
||||||
|
|
||||||
// Basic overflow check (very naive):
|
|
||||||
if (coefficient <= (LLONG_MAX - d) / 10) {
|
|
||||||
coefficient = coefficient * 10 + d;
|
|
||||||
} else {
|
|
||||||
// If you want to handle overflow by switching to float, do that here.
|
|
||||||
// For simplicity, let's just keep wrapping. In production, be careful!
|
|
||||||
coefficient = coefficient * 10 + d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
3) Check for decimal part
|
|
||||||
------------------------------------------- */
|
|
||||||
int has_decimal_point = 0;
|
|
||||||
int fraction_digits = 0;
|
|
||||||
|
|
||||||
if (*str == '.') {
|
|
||||||
has_decimal_point = 1;
|
|
||||||
str++;
|
|
||||||
while (*str >= '0' && *str <= '9') {
|
|
||||||
got_digits = 1;
|
|
||||||
int d = (*str - '0');
|
|
||||||
str++;
|
|
||||||
fraction_digits++;
|
|
||||||
if (coefficient <= (LLONG_MAX - d) / 10) {
|
|
||||||
coefficient = coefficient * 10 + d;
|
|
||||||
} else {
|
|
||||||
// Same naive overflow comment
|
|
||||||
coefficient = coefficient * 10 + d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
4) Check for exponent part
|
|
||||||
------------------------------------------- */
|
|
||||||
int exponent_negative = 0;
|
|
||||||
long long exponent_val = 0;
|
|
||||||
|
|
||||||
if (*str == 'e' || *str == 'E') {
|
|
||||||
str++;
|
|
||||||
if (*str == '+') {
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
else if (*str == '-') {
|
|
||||||
exponent_negative = 1;
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
while (*str >= '0' && *str <= '9') {
|
|
||||||
int d = (*str - '0');
|
|
||||||
str++;
|
|
||||||
if (exponent_val <= (LLONG_MAX - d) / 10) {
|
|
||||||
exponent_val = exponent_val * 10 + d;
|
|
||||||
} else {
|
|
||||||
// Again, naive overflow handling
|
|
||||||
exponent_val = exponent_val * 10 + d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
5) If there were no valid digits at all,
|
|
||||||
store 0 and return. (simple fallback)
|
|
||||||
------------------------------------------- */
|
|
||||||
if (!got_digits) {
|
|
||||||
nota_write_int_buf(nb, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
6) Combine fraction digits into exponent
|
|
||||||
final_exponent = exponent_val - fraction_digits
|
|
||||||
(apply exponent sign if any)
|
|
||||||
------------------------------------------- */
|
|
||||||
if (exponent_negative) {
|
|
||||||
exponent_val = -exponent_val;
|
|
||||||
}
|
|
||||||
long long final_exponent = exponent_val - fraction_digits;
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
7) Decide if we are storing an integer
|
|
||||||
or a float in Nota format.
|
|
||||||
-------------------------------------------
|
|
||||||
Rule used here:
|
|
||||||
- If there's no decimal point AND final_exponent == 0,
|
|
||||||
=> integer
|
|
||||||
- If we do have a decimal point, but fraction_digits == 0
|
|
||||||
and exponent_val == 0, then the user typed something
|
|
||||||
like "123." or "100.0". That is effectively an integer,
|
|
||||||
so store it as an integer if you want a purely numeric approach.
|
|
||||||
- Otherwise store as float.
|
|
||||||
------------------------------------------- */
|
|
||||||
|
|
||||||
// If "no decimal" => definitely integer:
|
|
||||||
// or decimal present but fraction_digits=0 & exponent_val=0 => integer
|
|
||||||
int treat_as_integer = 0;
|
|
||||||
if (!has_decimal_point && final_exponent == 0) {
|
|
||||||
treat_as_integer = 1;
|
|
||||||
}
|
|
||||||
else if (has_decimal_point && fraction_digits == 0 && exponent_val == 0) {
|
|
||||||
// Means "123." or "123.0"
|
|
||||||
treat_as_integer = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (treat_as_integer) {
|
|
||||||
// If negative => flip the sign in the stored value
|
|
||||||
if (negative) {
|
|
||||||
coefficient = -coefficient;
|
|
||||||
}
|
|
||||||
// Write the integer in Nota format (varint with sign bit)
|
|
||||||
nota_write_int_buf(nb, coefficient);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------
|
|
||||||
8) Write as float in Nota format
|
|
||||||
We do basically the same approach as
|
|
||||||
nota_write_float_buf does:
|
|
||||||
- NOTA_FLOAT nibble
|
|
||||||
- sign bit if negative
|
|
||||||
- exponent sign bit if final_exponent < 0
|
|
||||||
- varint of |final_exponent|
|
|
||||||
- varint of |coefficient|
|
|
||||||
------------------------------------------- */
|
|
||||||
{
|
|
||||||
char *p = nota_buffer_alloc(nb, 21); // Up to ~21 bytes worst-case
|
|
||||||
p[0] = NOTA_FLOAT;
|
|
||||||
if (negative) {
|
|
||||||
p[0] |= (1 << 3); // Mantissa sign bit
|
|
||||||
}
|
|
||||||
if (final_exponent < 0) {
|
|
||||||
p[0] |= (1 << 4); // Exponent sign bit
|
|
||||||
final_exponent = -final_exponent;
|
|
||||||
}
|
|
||||||
// Write exponent as varint (with 3 bits used in the first byte)
|
|
||||||
char *c = nota_continue_num(final_exponent, p, 3);
|
|
||||||
// Write the absolute coefficient (7 bits used in the first byte)
|
|
||||||
char *end = nota_continue_num(coefficient, c, 7);
|
|
||||||
|
|
||||||
// Adjust the buffer size to the actual used length
|
|
||||||
size_t used = (size_t)(end - p);
|
|
||||||
nb->size -= (21 - used);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void nota_write_number(NotaBuffer *nb, double n)
|
void nota_write_number(NotaBuffer *nb, double n)
|
||||||
{
|
{
|
||||||
nota_write_int_or_float_buf(nb, n);
|
nota_write_int_or_float_buf(nb, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write an integer in varint form (with sign bit) */
|
|
||||||
static void nota_write_int_buf(NotaBuffer *nb, long long n)
|
static void nota_write_int_buf(NotaBuffer *nb, long long n)
|
||||||
{
|
{
|
||||||
/* up to ~10 bytes for varint */
|
/* up to ~10 bytes for varint */
|
||||||
|
|||||||
@@ -146,19 +146,13 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
|
|||||||
int tag = JS_VALUE_GET_TAG(replaced);
|
int tag = JS_VALUE_GET_TAG(replaced);
|
||||||
|
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case JS_TAG_INT: {
|
case JS_TAG_INT:
|
||||||
|
case JS_TAG_FLOAT64: {
|
||||||
double d;
|
double d;
|
||||||
JS_ToFloat64(ctx, &d, replaced);
|
JS_ToFloat64(ctx, &d, replaced);
|
||||||
nota_write_number(&enc->nb, d);
|
nota_write_number(&enc->nb, d);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case JS_TAG_BIG_INT:
|
|
||||||
case JS_TAG_FLOAT64: {
|
|
||||||
const char *str = JS_ToCString(ctx, replaced);
|
|
||||||
nota_write_number_str(&enc->nb, str);
|
|
||||||
JS_FreeCString(ctx, str);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JS_TAG_STRING: {
|
case JS_TAG_STRING: {
|
||||||
const char *str = JS_ToCString(ctx, replaced);
|
const char *str = JS_ToCString(ctx, replaced);
|
||||||
nota_write_text(&enc->nb, str);
|
nota_write_text(&enc->nb, str);
|
||||||
|
|||||||
Reference in New Issue
Block a user