Frame cost was O(capacity x levels) once the ring saturated: acquire() saw the window base move (it always moves once the ring wraps), cleared the pyramid and re-appended all 1,048,576 retained samples, and append() walked every live level with no early stop -- 13,647,936 bucket merges and 73.8 ms per frame at a 1M-sample window, scaling with the window, which is what capped the 1 MS/s demo at 32-58 ms/frame while it was still filling. Index the pyramid in absolute bucket space instead. Level l bucket k covers the samples [k * (kBucket << l), (k + 1) * (kBucket << l)), so a bucket's span never depends on where the window starts and the window can slide over the aggregate instead of forcing it to be rebuilt. Each level keeps its complete buckets in a deque plus the absolute base of its first entry; append() folds a sample into the single in-progress level-0 bucket and, only when that bucket completes, cascades upward, stopping at the first level that gains no new entry; a new drop_before() slides the window by discarding leading buckets and remembers where the window begins. acquire() is now: drop to the new base, then append exactly what was produced -- 256 merges and 0.034 ms per frame for 8192 new samples, independent of the window, and memory bounded by the window (32,767 entries after 50M samples through a 1M ring, the same as after 10M). A producer that laps the consumer by more than capacity() between two acquires restarts the window rather than splicing a gap. Interface: `level(l) -> std::span<const MinMax>` cannot survive a ring and is replaced by level_base/level_size/bucket in absolute bucket coordinates, plus window_base/window_end/drop_before. The *_buckets queries stay in window coordinates (index 0 = oldest retained sample), so Series keeps its window semantics for callers; its exactness split now aligns in absolute space (window_base() + the ceil/floor) because the bucket grid is absolute. The sums move from per-level prefix sums to per-bucket ones -- the only representation a sliding front can keep exact -- and StreamSeries::y_bounds() becomes reduce(0, size()), exact in O(log n + kBucket) per frame instead of a scan of the window. The pyramid owns the published window now, so there is no second copy of base/end to keep in step, and gather() emits only buckets that are both complete and wholly retained. Tests: test-only merge counters (mirroring PLOT_PYRAMID_COUNT_COVERS, absent from every non-test build) pin an acquire proportional to production and independent of capacity, an append cost amortised over 16 live levels, and bounded memory; brute-force checks pin absolute bucket aggregates across slides, sliding-window reduce/sum/sumsq over unaligned ranges, and the level-1 gather mapping. The demo now draws 946 samples/px at 1.03 ms/frame (967 fps) where the saturated window cost 54.96 ms/frame (18 fps).
59 lines
1.8 KiB
CMake
59 lines
1.8 KiB
CMake
cmake_minimum_required(VERSION 3.24)
|
|
project(plot VERSION 0.1.0 LANGUAGES CXX)
|
|
|
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
|
include(plot_options)
|
|
|
|
add_library(plot_core
|
|
src/core/status.cpp
|
|
src/math/transform.cpp
|
|
src/data/minmax_pyramid.cpp
|
|
src/data/series.cpp
|
|
src/data/array_series.cpp
|
|
src/data/stream_series.cpp
|
|
src/gl/loader.cpp
|
|
src/gl/handle.cpp
|
|
src/render/shaders.cpp
|
|
src/render/bitmap_font.cpp
|
|
src/render/primitives.cpp
|
|
src/render/buffer.cpp
|
|
src/render/vertex_array.cpp
|
|
src/render/program.cpp
|
|
src/render/renderer.cpp
|
|
src/plot/ticks.cpp
|
|
src/plot/axis.cpp
|
|
src/plot/grid.cpp
|
|
src/plot/measure.cpp
|
|
src/plot/plot.cpp
|
|
src/input/controller.cpp
|
|
)
|
|
add_library(plot::core ALIAS plot_core)
|
|
|
|
target_include_directories(plot_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
|
target_compile_features(plot_core PUBLIC cxx_std_20)
|
|
target_link_libraries(plot_core PRIVATE ${CMAKE_DL_LIBS})
|
|
plot_apply_warnings(plot_core)
|
|
|
|
if(PLOT_BUILD_TESTS)
|
|
# Test-only: counts the pyramid's covering-decomposition entries and its bucket
|
|
# merges so a test can pin the query's O(log n) cost and the append path's
|
|
# amortised O(1) cost, neither of which any answer reveals. Off in every
|
|
# non-test build (see include/plot/data/detail/{cover,merge}_count.hpp).
|
|
target_compile_definitions(plot_core PRIVATE PLOT_PYRAMID_COUNT_COVERS
|
|
PLOT_PYRAMID_COUNT_MERGES)
|
|
|
|
enable_testing()
|
|
add_subdirectory(tests)
|
|
endif()
|
|
|
|
# Adapters own the surface and translate their host's events; examples link the
|
|
# adapter they demo. Both are opt-in, so the core library and its tests still
|
|
# build on a bare toolchain.
|
|
if(PLOT_BUILD_GLFW)
|
|
add_subdirectory(adapters/glfw)
|
|
endif()
|
|
|
|
if(PLOT_BUILD_EXAMPLES)
|
|
add_subdirectory(examples)
|
|
endif()
|