claude markdown
This commit is contained in:
@@ -0,0 +1,104 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Build and test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build
|
||||||
|
ctest --test-dir build --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything is warning-clean under `-Wall -Wextra -Wpedantic`; keep it that way.
|
||||||
|
|
||||||
|
The test suite is a single hand-rolled binary (no framework), so there is no
|
||||||
|
per-test filter: run `./build/tests/effector_tests` directly to see the section
|
||||||
|
headings (`rng`, `registry`, `scanlines`, `batch loading`) and the `ok`/`FAIL`
|
||||||
|
lines. To iterate on one area, comment out the other blocks in
|
||||||
|
`tests/core_tests.cpp` or add a new `std::cout << "section\n"` block — checks are
|
||||||
|
plain `check(condition, "description")` calls and `main()` returns the failure
|
||||||
|
count.
|
||||||
|
|
||||||
|
Headless render (no display needed, the fastest end-to-end smoke test):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
build/effector -o /tmp/out.png -s 800x600 --strip-height 6 --seed 1 a.jpg b.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the GUI from this shell
|
||||||
|
|
||||||
|
GTK picks Wayland from an inherited `WAYLAND_DISPLAY` and will not draw into an
|
||||||
|
Xvfb display, which silently yields black screenshots. Under Xvfb always use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
env -u WAYLAND_DISPLAY GDK_BACKEND=x11 DISPLAY=:79 build/effector ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### The plug-in contract is declarative
|
||||||
|
|
||||||
|
`Effect` (`src/core/effect.hpp`) exposes `params()` returning `std::vector<ParamSpec>`
|
||||||
|
(`src/core/params.hpp`). Those specs are the *only* thing the GUI and CLI know
|
||||||
|
about an effect: `src/gui/window.cpp` builds SpinButtons/Switches/DropDowns from
|
||||||
|
them and `src/cli/options.cpp` generates flags from them (`strip_height` →
|
||||||
|
`--strip-height`). Never add effect-specific branches to the GUI or the parser —
|
||||||
|
add a `ParamSpec` instead. Values travel as `ParamValues` (a
|
||||||
|
`map<string, variant<long,double,bool,string>>`), read back with
|
||||||
|
`paramInt`/`paramDouble`/`paramBool`/`paramChoice`.
|
||||||
|
|
||||||
|
Adding an effect: new `src/effects/<name>.{hpp,cpp}` subclassing `Effect`, one
|
||||||
|
line in the registry list in `src/core/registry.cpp`, one line in the
|
||||||
|
`effector_core` sources in `CMakeLists.txt`. Nothing else.
|
||||||
|
|
||||||
|
### Two link targets, deliberately
|
||||||
|
|
||||||
|
`effector_core` (static) holds image IO, params, effects, rng and links only
|
||||||
|
`gdk-pixbuf-2.0` + Threads — **no gtkmm, no display**. The `effector` executable
|
||||||
|
adds `src/main.cpp`, `src/cli/`, `src/gui/` and links gtkmm. Keep GTK out of
|
||||||
|
`src/core/` and `src/effects/`; the tests link the core alone and rely on this.
|
||||||
|
|
||||||
|
### Determinism is a contract
|
||||||
|
|
||||||
|
`Rng` (`src/core/rng.hpp`) is a hand-written xoshiro256\*\* rather than
|
||||||
|
`<random>`, because libstdc++ distributions are not portable across
|
||||||
|
implementations. Same sources + settings + seed must produce byte-identical
|
||||||
|
output on any machine — the tests assert this. Effects must draw *every* random
|
||||||
|
value from the `Rng&` they are handed, and must not depend on thread scheduling:
|
||||||
|
the scanlines effect first draws the whole strip plan serially from the rng,
|
||||||
|
then copies pixels in parallel.
|
||||||
|
|
||||||
|
### Threading model
|
||||||
|
|
||||||
|
- `parallelFor` (`src/core/parallel.hpp`) splits fixed contiguous chunks across
|
||||||
|
hardware threads — used for pixel work, where items cost the same.
|
||||||
|
- `loadImages` (`src/core/image.cpp`) deliberately does *not* use `parallelFor`:
|
||||||
|
files vary wildly in size, so workers pull the next index from a shared
|
||||||
|
`std::atomic` cursor. It reports through `LoadProgress::onFile` (called from
|
||||||
|
decoding threads: must be thread-safe and non-blocking) and aborts when
|
||||||
|
`LoadProgress::cancel` is set, in which case the partial result is discarded
|
||||||
|
by the caller, not used.
|
||||||
|
- The GUI never blocks the main thread. Both decoding and rendering run on a
|
||||||
|
`std::thread` and signal back with a `Glib::Dispatcher`
|
||||||
|
(`loadProgress_`/`loadFinished_`/`renderDone_` in `src/gui/window.hpp`); the
|
||||||
|
handlers are the only code that touches widgets. Renders are coalesced through
|
||||||
|
a debounce timer (`requestRender()` → `startRender()`), and the progress
|
||||||
|
window is created on a 250 ms delay so short batches never flash a dialog.
|
||||||
|
`loadWindow_` is rebuilt from scratch each time, so `loadBar_`/`loadFileLabel_`
|
||||||
|
are raw pointers that must be nulled in `closeLoadWindow()`.
|
||||||
|
- Sources are held as an immutable `shared_ptr<const SourceList>` and edited
|
||||||
|
copy-on-write via `mutateSources()`, so a render in flight can never race with
|
||||||
|
an add/remove.
|
||||||
|
|
||||||
|
### Image buffer
|
||||||
|
|
||||||
|
`Image` is tightly packed 8-bit RGB with stride exactly `width * 3` — effects
|
||||||
|
rely on this for `memcpy`, and GDK uploads the buffer as is. Decoding applies
|
||||||
|
EXIF orientation. `Image::load` throws; `loadImages` collects per-file
|
||||||
|
`LoadFailure`s instead.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
`python-reference/` is the original Python prototype, kept only for comparison.
|
||||||
|
Do not modify it or import from it.
|
||||||
Reference in New Issue
Block a user