* perf(timing): say which render-thread work a late frame followed The soak already says how often a moving frame reached the panel late, but not what the render thread was doing just before it. Vegas does two kinds of work there between frames -- building its strip (compose, extend) and, with live elements, patching changed pixels into it -- and deciding whether either is affordable needs their own numbers. - FrameTimingRecorder.note_op(kind, nbytes) tags the next presented frame. Totals gain op_frames, late_op_frames, op_freezes and op_bytes per kind; aggregate() still takes frames without ops. The file schema is unchanged. - Vegas tags compose and every strip extension (with the bytes it copied). - frame_soak prints an "after work" table: frames, late %, freezes and MB moved per kind, only when something tagged its work. - render_bench gains --strip-screens (Vegas-sized strips), --patch-bytes / --patch-every / --patch-where (in-place column writes, as a live element update does) and --extend-every-screens / --extend-width (append + trim on a fixed cadence that holds the strip's width). No runtime behaviour changes: this is the measurement gate for live Vegas elements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): note the frame-op attribution and bench modes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(scroll): build the strip's PIL image only when something reads it Every Vegas strip extension rebuilt ScrollHelper.cached_image from cached_array in full, twice (append, then trim), on the render thread: Image.fromarray is 1.7ms for an 8,000px strip and 3.8ms for 20,000px on a Pi 4 (measured on ledpi), about two thirds of an extension's render-thread cost. Nothing on the frame path reads the image's pixels; every frame is cut from the array. cached_image is now a property. append_content and drop_scrolled_prefix defer it; the first read builds it from the array it started with and keeps it only if the strip has not changed meanwhile, so a sync push racing an extension cannot leave a stale image cached. Assigning cached_image stores exactly what was assigned, as before. has_strip() says whether there is a strip without building its image; the helper's frame path, Vegas and the adapter's scroll-cache invalidation use it. The strip is also no longer held in memory twice. In Vegas the image is now built only by a multi-display sync push. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(vegas): live elements -- a plugin API for content that changes while it scrolls Vegas bakes each plugin's pictures into one strip, so a card already on its way across the panel keeps what it showed when it was drawn. This adds the API and bookkeeping for content that can be updated in place; the worker that redraws and swaps it follows separately. No shipped plugin implements the hook yet, so nothing changes for users. Plugin API (core 3.8.0), all no-ops by default: - BasePlugin.get_vegas_elements() -> [VegasElement(key, image, version, live, refresh_hz)]: named, fixed-width pieces of Vegas content. - BasePlugin.redraw_vegas_element(key, width, height, at): a lock-free redraw for content that changes with time. - BasePlugin.notify_vegas_data_changed(): data that lands outside update(). - src/plugin_system/vegas_elements.py (VegasElement, re-exported from base_plugin). Core: - PluginAdapter asks a plugin that implements the hook for elements on the background fetch only (under its lock, on its own canvas); every other path keeps get_vegas_content(). Live elements are pinned (padded with content_padding, never trimmed), tagged with their key, digest and data epoch in Image.info so the existing cache and group plumbing carry them unchanged, and untagged if a width budget crops them. - RenderPipeline records where each live element lands (ElementRecord), in absolute strip columns a trim does not move; the block-start arithmetic is shared with the STATIC markers. - PluginManager update listeners (add/remove_update_listener, notify_data_changed): told the moment update() completes, not at the next ~4s Vegas poll. The coordinator uses one to move each plugin's data epoch on. - vegas_scroll.live_refresh (kill switch), live_max_hz, live_min_interval, live_lead_screens; per-plugin core-owned vegas_live. Live elements are off under multi-display sync, in swap mode and with offscreen_prefetch off. - scripts/check_plugin.py checks the element contract (src/plugin_system/testing/vegas.py); test/fixtures/plugins/vegas-live-stub is a working example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
6.3 KiB
Core Plugin Properties
Overview
The LEDMatrix plugin system automatically manages certain core properties that are common to all plugins. These properties are handled by the system and don't need to be explicitly defined in plugin schemas.
Core Properties
The following properties are automatically managed by the system (the list
is CORE_PLUGIN_PROPERTIES in src/plugin_system/schema_manager.py):
-
enabled(boolean)- Default:
true - Description: Enable or disable the plugin
- System-managed by PluginManager
- Default:
-
display_duration(number)- Default:
15 - Range: 1-300 seconds
- Description: How long to display the plugin in seconds
- Can be overridden per-plugin
- Default:
-
live_priority(boolean)- Default:
false - Description: Enable live priority takeover when plugin has live content
- Used by DisplayController for priority scheduling
- Default:
-
vegas_width_pct,vegas_overflow,vegas_max_width_screens(untyped; no default)- Description: Vegas mode tuning for this plugin — card width as a
percentage of the panel,
"rotate"or"truncate"on overflow, and the widest the card may be in screens - Read by
src/vegas_mode/plugin_adapter.pyandBasePlugin, which validate the values themselves and ignore a bad one with a log line
- Description: Vegas mode tuning for this plugin — card width as a
percentage of the panel,
-
vegas_participation(string enum:"scroll","pause","exclude"; no default)- Description: how this plugin takes part in Vegas mode — its content scrolls by, the scroll pauses for its turn and shows it full screen, or it is left out
- Overrides the plugin's own default (its manifest's
vegas_participation, else what its legacy Vegas hooks say); unset means "use the plugin's default" - Deliberately has no default: one would be written into every plugin's config and override what each plugin declares
- Read by
resolve_vegas_participation()insrc/plugin_system/base_plugin.py; see PLUGIN_API_REFERENCE.md
-
vegas_live(boolean; no default, unset means on)- Description: for a plugin with live Vegas elements (it implements
get_vegas_elements()), whether the ticker changes what is already scrolling when the plugin's data changes.falseshows each card as it was when drawn, as before live elements existed - Ignored by plugins without live elements, and whenever live elements
are off for the whole ticker (
display.vegas_scroll.live_refresh) - Read by
PluginAdapter.is_live_capable()insrc/vegas_mode/plugin_adapter.py; see PLUGIN_API_REFERENCE.md
- Description: for a plugin with live Vegas elements (it implements
skin and skin_options were core properties until the skin system was
removed. A plugin config saved with them still loads and saves; the keys are
dropped on the next save (see RETIRED_PLUGIN_KEYS in schema_manager.py).
How Core Properties Work
Schema Validation
During configuration validation:
- Automatic Injection: Core properties are automatically injected into the validation schema if they're not already defined in the plugin's
config_schema.json - Removed from Required: Core properties are automatically removed from the
requiredarray during validation, since they're system-managed - Default Values Applied: If core properties are missing from a config, defaults are applied automatically:
enabled:true(matchesBasePlugin.__init__)display_duration:15(matchesBasePlugin.get_display_duration())live_priority:false(matchesBasePlugin.has_live_priority())
Plugin Schema Files
Plugin schemas can optionally include these properties for documentation purposes, but they're not required:
{
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable or disable this plugin"
},
"display_duration": {
"type": "number",
"default": 15,
"minimum": 1,
"maximum": 300,
"description": "Display duration in seconds"
},
"live_priority": {
"type": "boolean",
"default": false,
"description": "Enable live priority takeover"
}
},
"required": [] // Core properties should NOT be in required array
}
Important: Even if you include core properties in your schema, they should NOT be listed in the required array, as the system will automatically remove them during validation.
Configuration Files
Core properties are stored in the main config/config.json file:
{
"my-plugin": {
"enabled": true,
"display_duration": 20,
"live_priority": false,
"plugin_specific_setting": "value"
}
}
Implementation Details
SchemaManager
The SchemaManager.validate_config_against_schema() method:
- Injects core properties into the schema
propertiesif not present - Removes core properties from the
requiredarray - Validates the config against the enhanced schema
- Applies defaults for missing core properties
Default Merging
When generating default configurations or merging with defaults:
- Core properties get their system defaults if not in the schema
- User-provided values override system defaults
- Missing core properties are filled in automatically
Best Practices
- Don't require core properties: Never include
enabled,display_duration, orlive_priorityin your schema'srequiredarray - Optional inclusion: You can include core properties in your schema for documentation, but it's optional
- Use system defaults: Rely on system defaults unless your plugin needs specific values
- Document if included: If you include core properties in your schema, use the same defaults as the system to avoid confusion
Troubleshooting
"Missing required property 'enabled'" Error
This error should not occur with the current implementation. If you see it:
- Check that your schema doesn't have
enabledin therequiredarray - Ensure you're using the latest version of
SchemaManager - Verify the schema is being loaded correctly
Core Properties Not Working
If core properties aren't being applied:
- Check that defaults are being merged (see
save_plugin_config()) - Verify the schema manager is injecting core properties
- Check plugin initialization to ensure defaults are applied