Force remove all docs
This commit is contained in:
parent
cb47ab14c8
commit
677eaba2c1
File diff suppressed because it is too large
Load Diff
@ -1,695 +0,0 @@
|
|||||||
# Animation Development Guide
|
|
||||||
|
|
||||||
Guide for developers creating custom animation classes in the Berry Animation Framework.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
**Note**: This guide is for developers who want to extend the framework by creating new animation classes. For using existing animations, see the [DSL Reference](DSL_REFERENCE.md) which provides a declarative way to create animations without programming.
|
|
||||||
|
|
||||||
The Berry Animation Framework uses a unified architecture where all visual elements inherit from the base `Animation` class. This guide explains how to create custom animation classes that integrate seamlessly with the framework's parameter system, value providers, and rendering pipeline.
|
|
||||||
|
|
||||||
## Animation Class Structure
|
|
||||||
|
|
||||||
### Basic Class Template
|
|
||||||
|
|
||||||
```berry
|
|
||||||
#@ solidify:MyAnimation,weak
|
|
||||||
class MyAnimation : animation.animation
|
|
||||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
|
||||||
|
|
||||||
# Parameter definitions following the new specification
|
|
||||||
static var PARAMS = {
|
|
||||||
"my_param1": {"default": "default_value", "type": "string"},
|
|
||||||
"my_param2": {"min": 0, "max": 255, "default": 100, "type": "int"}
|
|
||||||
# Do NOT include inherited Animation parameters here
|
|
||||||
}
|
|
||||||
|
|
||||||
def init(engine)
|
|
||||||
# Engine parameter is MANDATORY and cannot be nil
|
|
||||||
super(self).init(engine)
|
|
||||||
|
|
||||||
# Only initialize non-parameter instance variables (none in this example)
|
|
||||||
# Parameters are handled by the virtual parameter system
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handle parameter changes (optional)
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
# Add custom logic for parameter changes if needed
|
|
||||||
# Parameter validation is handled automatically by the framework
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update animation state (no return value needed)
|
|
||||||
def update(time_ms)
|
|
||||||
super(self).update(time_ms)
|
|
||||||
# Your update logic here
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(frame, time_ms, strip_length)
|
|
||||||
if !self.is_running || frame == nil
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use virtual parameter access - automatically resolves ValueProviders
|
|
||||||
var param1 = self.my_param1
|
|
||||||
var param2 = self.my_param2
|
|
||||||
|
|
||||||
# Use strip_length parameter instead of self.engine.strip_length for performance
|
|
||||||
# Your rendering logic here
|
|
||||||
# ...
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
# NO setter methods needed - use direct virtual parameter assignment:
|
|
||||||
# obj.my_param1 = value
|
|
||||||
# obj.my_param2 = value
|
|
||||||
|
|
||||||
def tostring()
|
|
||||||
return f"MyAnimation(param1={self.my_param1}, param2={self.my_param2}, running={self.is_running})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## PARAMS System
|
|
||||||
|
|
||||||
### Static Parameter Definition
|
|
||||||
|
|
||||||
The `PARAMS` static variable defines all parameters specific to your animation class. This system provides:
|
|
||||||
|
|
||||||
- **Parameter validation** with min/max constraints and type checking
|
|
||||||
- **Default value handling** for initialization
|
|
||||||
- **Virtual parameter access** through getmember/setmember
|
|
||||||
- **Automatic ValueProvider resolution**
|
|
||||||
|
|
||||||
#### Parameter Definition Format
|
|
||||||
|
|
||||||
```berry
|
|
||||||
static var PARAMS = {
|
|
||||||
"parameter_name": {
|
|
||||||
"default": default_value, # Default value (optional)
|
|
||||||
"min": minimum_value, # Minimum value for integers (optional)
|
|
||||||
"max": maximum_value, # Maximum value for integers (optional)
|
|
||||||
"enum": [val1, val2, val3], # Valid enum values (optional)
|
|
||||||
"type": "parameter_type", # Expected type (optional)
|
|
||||||
"nillable": true # Whether nil values are allowed (optional)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Supported Types
|
|
||||||
|
|
||||||
- **`"int"`** - Integer values (default if not specified)
|
|
||||||
- **`"string"`** - String values
|
|
||||||
- **`"bool"`** - Boolean values (true/false)
|
|
||||||
- **`"bytes"`** - Bytes objects (validated using isinstance())
|
|
||||||
- **`"instance"`** - Object instances
|
|
||||||
- **`"any"`** - Any type (no type validation)
|
|
||||||
|
|
||||||
#### Important Rules
|
|
||||||
|
|
||||||
- **Do NOT include inherited parameters** - Animation base class parameters are handled automatically
|
|
||||||
- **Only define class-specific parameters** in your PARAMS
|
|
||||||
- **No constructor parameter mapping** - the new system uses engine-only constructors
|
|
||||||
- **Parameters are accessed via virtual members**: `obj.param_name`
|
|
||||||
|
|
||||||
## Constructor Implementation
|
|
||||||
|
|
||||||
### Engine-Only Constructor Pattern
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def init(engine)
|
|
||||||
# 1. ALWAYS call super with engine (engine is the ONLY parameter)
|
|
||||||
super(self).init(engine)
|
|
||||||
|
|
||||||
# 2. Initialize non-parameter instance variables only
|
|
||||||
self.internal_state = initial_value
|
|
||||||
self.buffer = nil
|
|
||||||
# Do NOT initialize parameters here - they are handled by the virtual system
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameter Change Handling
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
# Optional method to handle parameter changes
|
|
||||||
if name == "scale"
|
|
||||||
# Recalculate internal state when scale changes
|
|
||||||
self._update_internal_buffers()
|
|
||||||
elif name == "color"
|
|
||||||
# Handle color changes
|
|
||||||
self._invalidate_color_cache()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Changes from Old System
|
|
||||||
|
|
||||||
- **Engine-only constructor**: Constructor takes ONLY the engine parameter
|
|
||||||
- **No parameter initialization**: Parameters are set by caller using virtual member assignment
|
|
||||||
- **No instance variables for parameters**: Parameters are handled by the virtual system
|
|
||||||
- **Automatic validation**: Parameter validation happens automatically based on PARAMS constraints
|
|
||||||
|
|
||||||
## Value Provider Integration
|
|
||||||
|
|
||||||
### Automatic ValueProvider Resolution
|
|
||||||
|
|
||||||
The virtual parameter system automatically resolves ValueProviders when you access parameters:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def render(frame, time_ms, strip_length)
|
|
||||||
# Virtual parameter access automatically resolves ValueProviders
|
|
||||||
var color = self.color # Returns current color value, not the provider
|
|
||||||
var position = self.pos # Returns current position value
|
|
||||||
var size = self.size # Returns current size value
|
|
||||||
|
|
||||||
# Use strip_length parameter (computed once by engine_proxy) instead of self.engine.strip_length
|
|
||||||
# Use resolved values in rendering logic
|
|
||||||
for i: position..(position + size - 1)
|
|
||||||
if i >= 0 && i < strip_length
|
|
||||||
frame.set_pixel_color(i, color)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting Dynamic Parameters
|
|
||||||
|
|
||||||
Users can set both static values and ValueProviders using the same syntax:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Create animation
|
|
||||||
var anim = animation.my_animation(engine)
|
|
||||||
|
|
||||||
# Static values
|
|
||||||
anim.color = 0xFFFF0000
|
|
||||||
anim.pos = 5
|
|
||||||
anim.size = 3
|
|
||||||
|
|
||||||
# Dynamic values
|
|
||||||
anim.color = animation.smooth(0xFF000000, 0xFFFFFFFF, 2000)
|
|
||||||
anim.pos = animation.triangle(0, 29, 3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Optimization
|
|
||||||
|
|
||||||
For performance-critical code, cache parameter values:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def render(frame, time_ms, strip_length)
|
|
||||||
# Cache parameter values to avoid multiple virtual member access
|
|
||||||
var current_color = self.color
|
|
||||||
var current_pos = self.pos
|
|
||||||
var current_size = self.size
|
|
||||||
|
|
||||||
# Use cached values in loops
|
|
||||||
for i: current_pos..(current_pos + current_size - 1)
|
|
||||||
if i >= 0 && i < strip_length
|
|
||||||
frame.set_pixel_color(i, current_color)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Color Provider LUT Optimization
|
|
||||||
|
|
||||||
For color providers that perform expensive color calculations (like palette interpolation), the base `ColorProvider` class provides a Lookup Table (LUT) mechanism for caching pre-computed colors:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
#@ solidify:MyColorProvider,weak
|
|
||||||
class MyColorProvider : animation.color_provider
|
|
||||||
# Instance variables (all should start with underscore)
|
|
||||||
var _cached_data # Your custom cached data
|
|
||||||
|
|
||||||
def init(engine)
|
|
||||||
super(self).init(engine) # Initializes _color_lut and _lut_dirty
|
|
||||||
self._cached_data = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Mark LUT as dirty when parameters change
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
super(self).on_param_changed(name, value)
|
|
||||||
if name == "palette" || name == "transition_type"
|
|
||||||
self._lut_dirty = true # Inherited from ColorProvider
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Rebuild LUT when needed
|
|
||||||
def _rebuild_color_lut()
|
|
||||||
# Allocate LUT (e.g., 129 entries * 4 bytes = 516 bytes)
|
|
||||||
if self._color_lut == nil
|
|
||||||
self._color_lut = bytes()
|
|
||||||
self._color_lut.resize(129 * 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Pre-compute colors for values 0, 2, 4, ..., 254, 255
|
|
||||||
var i = 0
|
|
||||||
while i < 128
|
|
||||||
var value = i * 2
|
|
||||||
var color = self._compute_color_expensive(value)
|
|
||||||
self._color_lut.set(i * 4, color, 4)
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add final entry for value 255
|
|
||||||
var color_255 = self._compute_color_expensive(255)
|
|
||||||
self._color_lut.set(128 * 4, color_255, 4)
|
|
||||||
|
|
||||||
self._lut_dirty = false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update method checks if LUT needs rebuilding
|
|
||||||
def update(time_ms)
|
|
||||||
if self._lut_dirty || self._color_lut == nil
|
|
||||||
self._rebuild_color_lut()
|
|
||||||
end
|
|
||||||
return self.is_running
|
|
||||||
end
|
|
||||||
|
|
||||||
# Fast color lookup using LUT
|
|
||||||
def get_color_for_value(value, time_ms)
|
|
||||||
# Build LUT if needed (lazy initialization)
|
|
||||||
if self._lut_dirty || self._color_lut == nil
|
|
||||||
self._rebuild_color_lut()
|
|
||||||
end
|
|
||||||
|
|
||||||
# Map value to LUT index (divide by 2, special case for 255)
|
|
||||||
var lut_index = value >> 1
|
|
||||||
if value >= 255
|
|
||||||
lut_index = 128
|
|
||||||
end
|
|
||||||
|
|
||||||
# Retrieve pre-computed color from LUT
|
|
||||||
var color = self._color_lut.get(lut_index * 4, 4)
|
|
||||||
|
|
||||||
# Apply brightness scaling using static method (only if not 255)
|
|
||||||
var brightness = self.brightness
|
|
||||||
if brightness != 255
|
|
||||||
return animation.color_provider.apply_brightness(color, brightness)
|
|
||||||
end
|
|
||||||
|
|
||||||
return color
|
|
||||||
end
|
|
||||||
|
|
||||||
# Access LUT from outside (returns bytes() or nil)
|
|
||||||
# Inherited from ColorProvider: get_lut()
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**LUT Benefits:**
|
|
||||||
- **5-10x speedup** for expensive color calculations
|
|
||||||
- **Reduced CPU usage** during rendering
|
|
||||||
- **Smooth animations** even with complex color logic
|
|
||||||
- **Memory efficient** (typically 516 bytes for 129 entries)
|
|
||||||
|
|
||||||
**When to use LUT:**
|
|
||||||
- Palette interpolation with binary search
|
|
||||||
- Complex color transformations
|
|
||||||
- Brightness calculations
|
|
||||||
- Any expensive per-pixel color computation
|
|
||||||
|
|
||||||
**LUT Guidelines:**
|
|
||||||
- Store colors at maximum brightness, apply scaling after lookup
|
|
||||||
- Use 2-step resolution (0, 2, 4, ..., 254, 255) to save memory
|
|
||||||
- Invalidate LUT when parameters affecting color calculation change
|
|
||||||
- Don't invalidate for brightness changes if brightness is applied post-lookup
|
|
||||||
|
|
||||||
**Brightness Handling:**
|
|
||||||
|
|
||||||
The `ColorProvider` base class includes a `brightness` parameter (0-255, default 255) and a static method for applying brightness scaling:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Static method for brightness scaling (only scales if brightness != 255)
|
|
||||||
animation.color_provider.apply_brightness(color, brightness)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Best Practices:**
|
|
||||||
- Store LUT colors at maximum brightness (255)
|
|
||||||
- Apply brightness scaling after LUT lookup using the static method
|
|
||||||
- Only call the static method if `brightness != 255` to avoid unnecessary overhead
|
|
||||||
- For inline performance-critical code, you can inline the brightness calculation instead of calling the static method
|
|
||||||
- Brightness changes do NOT invalidate the LUT since brightness is applied after lookup
|
|
||||||
|
|
||||||
## Parameter Access
|
|
||||||
|
|
||||||
### Direct Virtual Member Assignment
|
|
||||||
|
|
||||||
The new system uses direct parameter assignment instead of setter methods:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Create animation
|
|
||||||
var anim = animation.my_animation(engine)
|
|
||||||
|
|
||||||
# Direct parameter assignment (recommended)
|
|
||||||
anim.color = 0xFF00FF00
|
|
||||||
anim.pos = 10
|
|
||||||
anim.size = 5
|
|
||||||
|
|
||||||
# Method chaining is not needed - just set parameters directly
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameter Validation
|
|
||||||
|
|
||||||
The parameter system handles validation automatically based on PARAMS constraints:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# This will raise an exception due to min: 0 constraint
|
|
||||||
anim.size = -1 # Raises value_error
|
|
||||||
|
|
||||||
# This will be accepted
|
|
||||||
anim.size = 5 # Parameter updated successfully
|
|
||||||
|
|
||||||
# Method-based setting returns true/false for validation
|
|
||||||
var success = anim.set_param("size", -1) # Returns false, no exception
|
|
||||||
```
|
|
||||||
|
|
||||||
### Accessing Raw Parameters
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Get current parameter value (resolved if ValueProvider)
|
|
||||||
var current_color = anim.color
|
|
||||||
|
|
||||||
# Get raw parameter (returns ValueProvider if set)
|
|
||||||
var raw_color = anim.get_param("color")
|
|
||||||
|
|
||||||
# Check if parameter is a ValueProvider
|
|
||||||
if animation.is_value_provider(raw_color)
|
|
||||||
print("Color is dynamic")
|
|
||||||
else
|
|
||||||
print("Color is static")
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rendering Implementation
|
|
||||||
|
|
||||||
### Frame Buffer Operations
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def render(frame, time_ms, strip_length)
|
|
||||||
if !self.is_running || frame == nil
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Resolve dynamic parameters
|
|
||||||
var color = self.resolve_value(self.color, "color", time_ms)
|
|
||||||
var opacity = self.resolve_value(self.opacity, "opacity", time_ms)
|
|
||||||
|
|
||||||
# Render your effect using strip_length parameter
|
|
||||||
for i: 0..(strip_length-1)
|
|
||||||
var pixel_color = calculate_pixel_color(i, time_ms)
|
|
||||||
frame.set_pixel_color(i, pixel_color)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Apply opacity if not full (supports numbers, animations)
|
|
||||||
if opacity < 255
|
|
||||||
frame.apply_opacity(opacity)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true # Frame was modified
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Rendering Patterns
|
|
||||||
|
|
||||||
#### Fill Pattern
|
|
||||||
```berry
|
|
||||||
# Fill entire frame with color
|
|
||||||
frame.fill_pixels(color)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Position-Based Effects
|
|
||||||
```berry
|
|
||||||
# Render at specific positions
|
|
||||||
var start_pos = self.resolve_value(self.pos, "pos", time_ms)
|
|
||||||
var size = self.resolve_value(self.size, "size", time_ms)
|
|
||||||
|
|
||||||
for i: 0..(size-1)
|
|
||||||
var pixel_pos = start_pos + i
|
|
||||||
if pixel_pos >= 0 && pixel_pos < frame.width
|
|
||||||
frame.set_pixel_color(pixel_pos, color)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Gradient Effects
|
|
||||||
```berry
|
|
||||||
# Create gradient across frame
|
|
||||||
for i: 0..(frame.width-1)
|
|
||||||
var progress = i / (frame.width - 1.0) # 0.0 to 1.0
|
|
||||||
var interpolated_color = interpolate_color(start_color, end_color, progress)
|
|
||||||
frame.set_pixel_color(i, interpolated_color)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complete Example: BeaconAnimation
|
|
||||||
|
|
||||||
Here's a complete example showing all concepts:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
#@ solidify:BeaconAnimation,weak
|
|
||||||
class BeaconAnimation : animation.animation
|
|
||||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
|
||||||
|
|
||||||
# Parameter definitions following the new specification
|
|
||||||
static var PARAMS = {
|
|
||||||
"color": {"default": 0xFFFFFFFF},
|
|
||||||
"back_color": {"default": 0xFF000000},
|
|
||||||
"pos": {"default": 0},
|
|
||||||
"beacon_size": {"min": 0, "default": 1},
|
|
||||||
"slew_size": {"min": 0, "default": 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Initialize a new Pulse Position animation
|
|
||||||
# Engine parameter is MANDATORY and cannot be nil
|
|
||||||
def init(engine)
|
|
||||||
# Call parent constructor with engine (engine is the ONLY parameter)
|
|
||||||
super(self).init(engine)
|
|
||||||
|
|
||||||
# Only initialize non-parameter instance variables (none in this case)
|
|
||||||
# Parameters are handled by the virtual parameter system
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handle parameter changes (optional - can be removed if no special handling needed)
|
|
||||||
def on_param_changed(name, value)
|
|
||||||
# No special handling needed for this animation
|
|
||||||
# Parameter validation is handled automatically by the framework
|
|
||||||
end
|
|
||||||
|
|
||||||
# Render the pulse to the provided frame buffer
|
|
||||||
def render(frame, time_ms, strip_length)
|
|
||||||
if frame == nil
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
var pixel_size = strip_length
|
|
||||||
# Use virtual parameter access - automatically resolves ValueProviders
|
|
||||||
var back_color = self.back_color
|
|
||||||
var pos = self.pos
|
|
||||||
var slew_size = self.slew_size
|
|
||||||
var beacon_size = self.beacon_size
|
|
||||||
var color = self.color
|
|
||||||
|
|
||||||
# Fill background if not transparent
|
|
||||||
if back_color != 0xFF000000
|
|
||||||
frame.fill_pixels(back_color)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calculate pulse boundaries
|
|
||||||
var pulse_min = pos
|
|
||||||
var pulse_max = pos + beacon_size
|
|
||||||
|
|
||||||
# Clamp to frame boundaries
|
|
||||||
if pulse_min < 0
|
|
||||||
pulse_min = 0
|
|
||||||
end
|
|
||||||
if pulse_max >= pixel_size
|
|
||||||
pulse_max = pixel_size
|
|
||||||
end
|
|
||||||
|
|
||||||
# Draw the main pulse
|
|
||||||
var i = pulse_min
|
|
||||||
while i < pulse_max
|
|
||||||
frame.set_pixel_color(i, color)
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Draw slew regions if slew_size > 0
|
|
||||||
if slew_size > 0
|
|
||||||
# Left slew (fade from background to pulse color)
|
|
||||||
var left_slew_min = pos - slew_size
|
|
||||||
var left_slew_max = pos
|
|
||||||
|
|
||||||
if left_slew_min < 0
|
|
||||||
left_slew_min = 0
|
|
||||||
end
|
|
||||||
if left_slew_max >= pixel_size
|
|
||||||
left_slew_max = pixel_size
|
|
||||||
end
|
|
||||||
|
|
||||||
i = left_slew_min
|
|
||||||
while i < left_slew_max
|
|
||||||
# Calculate blend factor
|
|
||||||
var blend_factor = tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0)
|
|
||||||
var alpha = 255 - blend_factor
|
|
||||||
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
|
|
||||||
var blended_color = frame.blend(back_color, blend_color)
|
|
||||||
frame.set_pixel_color(i, blended_color)
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Right slew (fade from pulse color to background)
|
|
||||||
var right_slew_min = pos + beacon_size
|
|
||||||
var right_slew_max = pos + beacon_size + slew_size
|
|
||||||
|
|
||||||
if right_slew_min < 0
|
|
||||||
right_slew_min = 0
|
|
||||||
end
|
|
||||||
if right_slew_max >= pixel_size
|
|
||||||
right_slew_max = pixel_size
|
|
||||||
end
|
|
||||||
|
|
||||||
i = right_slew_min
|
|
||||||
while i < right_slew_max
|
|
||||||
# Calculate blend factor
|
|
||||||
var blend_factor = tasmota.scale_uint(i, pos + beacon_size, pos + beacon_size + slew_size - 1, 0, 255)
|
|
||||||
var alpha = 255 - blend_factor
|
|
||||||
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
|
|
||||||
var blended_color = frame.blend(back_color, blend_color)
|
|
||||||
frame.set_pixel_color(i, blended_color)
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
# NO setter methods - use direct virtual parameter assignment instead:
|
|
||||||
# obj.color = value
|
|
||||||
# obj.pos = value
|
|
||||||
# obj.beacon_size = value
|
|
||||||
# obj.slew_size = value
|
|
||||||
|
|
||||||
# String representation of the animation
|
|
||||||
def tostring()
|
|
||||||
return f"BeaconAnimation(color=0x{self.color :08x}, pos={self.pos}, beacon_size={self.beacon_size}, slew_size={self.slew_size})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Export class directly - no redundant factory function needed
|
|
||||||
return {'beacon_animation': BeaconAnimation}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Your Animation
|
|
||||||
|
|
||||||
### Unit Tests
|
|
||||||
|
|
||||||
Create comprehensive tests for your animation:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import animation
|
|
||||||
|
|
||||||
def test_my_animation()
|
|
||||||
# Create LED strip and engine for testing
|
|
||||||
var strip = global.Leds(10) # Use built-in LED strip for testing
|
|
||||||
var engine = animation.create_engine(strip)
|
|
||||||
|
|
||||||
# Test basic construction
|
|
||||||
var anim = animation.my_animation(engine)
|
|
||||||
assert(anim != nil, "Animation should be created")
|
|
||||||
|
|
||||||
# Test parameter setting
|
|
||||||
anim.color = 0xFFFF0000
|
|
||||||
assert(anim.color == 0xFFFF0000, "Color should be set")
|
|
||||||
|
|
||||||
# Test parameter updates
|
|
||||||
anim.color = 0xFF00FF00
|
|
||||||
assert(anim.color == 0xFF00FF00, "Color should be updated")
|
|
||||||
|
|
||||||
# Test value providers
|
|
||||||
var dynamic_color = animation.smooth(engine)
|
|
||||||
dynamic_color.min_value = 0xFF000000
|
|
||||||
dynamic_color.max_value = 0xFFFFFFFF
|
|
||||||
dynamic_color.duration = 2000
|
|
||||||
|
|
||||||
anim.color = dynamic_color
|
|
||||||
var raw_color = anim.get_param("color")
|
|
||||||
assert(animation.is_value_provider(raw_color), "Should accept value provider")
|
|
||||||
|
|
||||||
# Test rendering
|
|
||||||
var frame = animation.frame_buffer(10)
|
|
||||||
anim.start()
|
|
||||||
var result = anim.render(frame, 1000, engine.strip_length)
|
|
||||||
assert(result == true, "Should render successfully")
|
|
||||||
|
|
||||||
print("✓ All tests passed")
|
|
||||||
end
|
|
||||||
|
|
||||||
test_my_animation()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration Testing
|
|
||||||
|
|
||||||
Test with the animation engine:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
var strip = global.Leds(30) # Use built-in LED strip
|
|
||||||
var engine = animation.create_engine(strip)
|
|
||||||
var anim = animation.my_animation(engine)
|
|
||||||
|
|
||||||
# Set parameters
|
|
||||||
anim.color = 0xFFFF0000
|
|
||||||
anim.pos = 5
|
|
||||||
anim.beacon_size = 3
|
|
||||||
|
|
||||||
engine.add(anim) # Unified method for animations and sequence managers
|
|
||||||
engine.run()
|
|
||||||
|
|
||||||
# Let it run for a few seconds
|
|
||||||
tasmota.delay(3000)
|
|
||||||
|
|
||||||
engine.stop()
|
|
||||||
print("Integration test completed")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- **Minimize calculations** in render() method
|
|
||||||
- **Cache resolved values** when possible
|
|
||||||
- **Use integer math** instead of floating point
|
|
||||||
- **Avoid memory allocation** in render loops
|
|
||||||
|
|
||||||
### Memory Management
|
|
||||||
- **Reuse objects** when possible
|
|
||||||
- **Clear references** to large objects when done
|
|
||||||
- **Use static variables** for constants
|
|
||||||
|
|
||||||
### Code Organization
|
|
||||||
- **Group related parameters** together
|
|
||||||
- **Use descriptive variable names**
|
|
||||||
- **Comment complex algorithms**
|
|
||||||
- **Follow Berry naming conventions**
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
- **Validate parameters** in constructor
|
|
||||||
- **Handle edge cases** gracefully
|
|
||||||
- **Return false** from render() on errors
|
|
||||||
- **Use meaningful error messages**
|
|
||||||
|
|
||||||
## Publishing Your Animation Class
|
|
||||||
|
|
||||||
Once you've created a new animation class:
|
|
||||||
|
|
||||||
1. **Add it to the animation module** by importing it in `animation.be`
|
|
||||||
2. **Create a factory function** following the engine-first pattern
|
|
||||||
3. **Add DSL support** by ensuring the transpiler recognizes your factory function
|
|
||||||
4. **Document parameters** in the class hierarchy documentation
|
|
||||||
5. **Test with DSL** to ensure users can access your animation declaratively
|
|
||||||
|
|
||||||
**Remember**: Users should primarily interact with animations through the DSL. The programmatic API is mainly for framework development and advanced integrations.
|
|
||||||
|
|
||||||
This guide provides everything needed to create professional-quality animation classes that integrate seamlessly with the Berry Animation Framework's parameter system and rendering pipeline.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,798 +0,0 @@
|
|||||||
# DSL Reference - Berry Animation Framework
|
|
||||||
|
|
||||||
This document provides a comprehensive reference for the Animation DSL (Domain-Specific Language), which allows you to define animations using a declarative syntax with named parameters.
|
|
||||||
|
|
||||||
## Module Import
|
|
||||||
|
|
||||||
The DSL functionality is provided by a separate module:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import animation # Core framework (required)
|
|
||||||
import animation_dsl # DSL compiler and runtime (required for DSL)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why Use the DSL?
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- **Declarative syntax**: Describe what you want, not how to implement it
|
|
||||||
- **Readable code**: Natural language-like syntax
|
|
||||||
- **Rapid prototyping**: Quick iteration on animation ideas
|
|
||||||
- **Event-driven**: Built-in support for interactive animations
|
|
||||||
- **Composition**: Easy layering and sequencing of animations
|
|
||||||
|
|
||||||
### When to Use DSL vs Programmatic
|
|
||||||
|
|
||||||
**Use DSL when:**
|
|
||||||
- Creating complex animation sequences
|
|
||||||
- Building interactive, event-driven animations
|
|
||||||
- Rapid prototyping and experimentation
|
|
||||||
- Non-programmers need to create animations
|
|
||||||
- You want declarative, readable animation definitions
|
|
||||||
|
|
||||||
**Use programmatic API when:**
|
|
||||||
- Building reusable animation components
|
|
||||||
- Performance is critical (DSL has compilation overhead)
|
|
||||||
- You need fine-grained control over animation logic
|
|
||||||
- Integrating with existing Berry code
|
|
||||||
- Firmware size is constrained (DSL module can be excluded)
|
|
||||||
|
|
||||||
## Transpiler Architecture
|
|
||||||
|
|
||||||
For detailed information about the DSL transpiler's internal architecture, including the core processing flow and expression processing chain, see [TRANSPILER_ARCHITECTURE.md](TRANSPILER_ARCHITECTURE.md).
|
|
||||||
|
|
||||||
## DSL API Functions
|
|
||||||
|
|
||||||
### Core Functions
|
|
||||||
|
|
||||||
#### `animation_dsl.compile(source)`
|
|
||||||
Compiles DSL source code to Berry code without executing it.
|
|
||||||
|
|
||||||
```berry
|
|
||||||
var dsl_source = "color red = 0xFF0000\n"
|
|
||||||
"animation red_anim = solid(color=red)\n"
|
|
||||||
"run red_anim"
|
|
||||||
|
|
||||||
var berry_code = animation_dsl.compile(dsl_source)
|
|
||||||
print(berry_code) # Shows generated Berry code
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `animation_dsl.execute(source)`
|
|
||||||
Compiles and executes DSL source code in one step.
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation_dsl.execute("color blue = 0x0000FF\n"
|
|
||||||
"animation blue_anim = solid(color=blue)\n"
|
|
||||||
"run blue_anim for 5s")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `animation_dsl.load_file(filename)`
|
|
||||||
Loads DSL source from a file and executes it.
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Create a DSL file
|
|
||||||
var f = open("my_animation.dsl", "w")
|
|
||||||
f.write("color green = 0x00FF00\n"
|
|
||||||
"animation pulse_green = pulsating_animation(color=green, period=2s)\n"
|
|
||||||
"run pulse_green")
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# Load and execute
|
|
||||||
animation_dsl.load_file("my_animation.dsl")
|
|
||||||
```
|
|
||||||
|
|
||||||
## DSL Language Overview
|
|
||||||
|
|
||||||
The Animation DSL uses a declarative syntax with named parameters. All animations are created with an engine-first pattern and parameters are set individually for maximum flexibility.
|
|
||||||
|
|
||||||
### Key Syntax Features
|
|
||||||
|
|
||||||
- **Import statements**: `import module_name` for loading Berry modules
|
|
||||||
- **Named parameters**: All function calls use `name=value` syntax
|
|
||||||
- **Time units**: `2s`, `500ms`, `1m`, `1h`
|
|
||||||
- **Hex colors**: `0xFF0000`, `0x80FF0000` (ARGB)
|
|
||||||
- **Named colors**: `red`, `blue`, `white`, etc.
|
|
||||||
- **Comments**: `# This is a comment`
|
|
||||||
- **Property assignment**: `animation.property = value`
|
|
||||||
- **User functions**: `function_name()` for custom functions
|
|
||||||
|
|
||||||
### Basic Structure
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Import statements (optional, for user functions or custom modules)
|
|
||||||
import user_functions
|
|
||||||
|
|
||||||
# Optional strip configuration
|
|
||||||
strip length 60
|
|
||||||
|
|
||||||
# Color definitions
|
|
||||||
color red = 0xFF0000
|
|
||||||
color blue = 0x0000FF
|
|
||||||
|
|
||||||
# Animation definitions with named parameters
|
|
||||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
|
||||||
animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500)
|
|
||||||
|
|
||||||
# Property assignments with user functions
|
|
||||||
pulse_red.priority = 10
|
|
||||||
pulse_red.opacity = breathing_effect()
|
|
||||||
comet_blue.direction = -1
|
|
||||||
|
|
||||||
# Execution
|
|
||||||
run pulse_red
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually.
|
|
||||||
|
|
||||||
## Symbol Resolution
|
|
||||||
|
|
||||||
The DSL transpiler uses intelligent symbol resolution at compile time to optimize generated code and eliminate runtime lookups:
|
|
||||||
|
|
||||||
### Transpile-Time Symbol Resolution
|
|
||||||
|
|
||||||
When the DSL encounters an identifier (like `SINE` or `red`), it checks at transpile time whether the symbol exists in the `animation` module using Berry's introspection capabilities:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# If SINE exists in animation module
|
|
||||||
animation wave = wave_animation(waveform=SINE)
|
|
||||||
# Transpiles to: animation.SINE (direct access)
|
|
||||||
|
|
||||||
# If custom_color doesn't exist in animation module
|
|
||||||
color custom_color = 0xFF0000
|
|
||||||
animation solid_red = solid(color=custom_color)
|
|
||||||
# Transpiles to: custom_color_ (user-defined variable)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
|
|
||||||
- **Performance**: Eliminates runtime symbol lookups for built-in constants
|
|
||||||
- **Error Detection**: Catches undefined symbols at compile time
|
|
||||||
- **Code Clarity**: Generated Berry code clearly shows built-in vs user-defined symbols
|
|
||||||
- **Optimization**: Direct access to animation module symbols is faster
|
|
||||||
|
|
||||||
### Symbol Categories
|
|
||||||
|
|
||||||
**Built-in Symbols** (resolved to `animation.<symbol>`):
|
|
||||||
- Animation factory functions: `solid`, `pulsating_animation`, `comet_animation`
|
|
||||||
- Value providers: `triangle`, `smooth`, `sine`, `static_value`
|
|
||||||
- Color providers: `color_cycle`, `breathe_color`, `rich_palette`
|
|
||||||
- Constants: `PALETTE_RAINBOW`, `SINE`, `TRIANGLE`, etc.
|
|
||||||
|
|
||||||
**User-defined Symbols** (resolved to `<symbol>_`):
|
|
||||||
- Custom colors: `my_red`, `fire_color`
|
|
||||||
- Custom animations: `pulse_effect`, `rainbow_wave`
|
|
||||||
- Variables: `brightness_level`, `cycle_time`
|
|
||||||
|
|
||||||
### Property Assignment Resolution
|
|
||||||
|
|
||||||
Property assignments also use the same resolution logic:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Built-in symbol (if 'engine' existed in animation module)
|
|
||||||
engine.brightness = 200
|
|
||||||
# Would transpile to: animation.engine.brightness = 200
|
|
||||||
|
|
||||||
# User-defined symbol
|
|
||||||
my_animation.priority = 10
|
|
||||||
# Transpiles to: my_animation_.priority = 10
|
|
||||||
```
|
|
||||||
|
|
||||||
This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code.
|
|
||||||
|
|
||||||
## Import Statement Transpilation
|
|
||||||
|
|
||||||
The DSL supports importing Berry modules using the `import` keyword, which provides a clean way to load user functions and custom modules.
|
|
||||||
|
|
||||||
### Import Syntax
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Import Syntax
|
|
||||||
import user_functions
|
|
||||||
import my_custom_module
|
|
||||||
import math
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transpilation Behavior
|
|
||||||
|
|
||||||
Import statements are transpiled directly to Berry import statements with quoted module names:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Code
|
|
||||||
import user_functions
|
|
||||||
|
|
||||||
# Transpiles to Berry Code
|
|
||||||
import "user_functions"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import Processing
|
|
||||||
|
|
||||||
1. **Early Processing**: Import statements are processed early in transpilation
|
|
||||||
2. **Module Loading**: Imported modules are loaded using standard Berry import mechanism
|
|
||||||
3. **Function Registration**: User function modules should register functions using `animation.register_user_function()`
|
|
||||||
4. **No Validation**: The DSL doesn't validate module existence at compile time
|
|
||||||
|
|
||||||
### Example Import Workflow
|
|
||||||
|
|
||||||
**Step 1: Create User Functions Module (`user_functions.be`)**
|
|
||||||
```berry
|
|
||||||
import animation
|
|
||||||
|
|
||||||
def rand_demo(engine)
|
|
||||||
import math
|
|
||||||
return math.rand() % 256
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register for DSL use
|
|
||||||
animation.register_user_function("rand_demo", rand_demo)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: Use in DSL**
|
|
||||||
```berry
|
|
||||||
import user_functions
|
|
||||||
|
|
||||||
animation test = solid(color=blue)
|
|
||||||
test.opacity = rand_demo()
|
|
||||||
run test
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: Generated Berry Code**
|
|
||||||
```berry
|
|
||||||
import animation
|
|
||||||
var engine = animation.init_strip()
|
|
||||||
|
|
||||||
import "user_functions"
|
|
||||||
var test_ = animation.solid(engine)
|
|
||||||
test_.color = 0xFF0000FF
|
|
||||||
test_.opacity = animation.create_closure_value(engine,
|
|
||||||
def (engine) return animation.get_user_function('rand_demo')(engine) end)
|
|
||||||
engine.add(test_)
|
|
||||||
engine.run()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Berry Code Block Transpilation
|
|
||||||
|
|
||||||
The DSL supports embedding arbitrary Berry code using the `berry` keyword with triple-quoted strings. This provides an escape hatch for complex logic while maintaining the declarative nature of the DSL.
|
|
||||||
|
|
||||||
### Berry Code Block Syntax
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Berry Code Block
|
|
||||||
berry """
|
|
||||||
import math
|
|
||||||
var custom_value = math.pi * 2
|
|
||||||
print("Custom calculation:", custom_value)
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transpilation Behavior
|
|
||||||
|
|
||||||
Berry code blocks are copied verbatim to the generated Berry code with comment markers:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Code
|
|
||||||
berry """
|
|
||||||
var test_var = 42
|
|
||||||
print("Hello from berry block")
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Transpiles to Berry Code
|
|
||||||
# Berry code block
|
|
||||||
var test_var = 42
|
|
||||||
print("Hello from berry block")
|
|
||||||
# End berry code block
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration with DSL Objects
|
|
||||||
|
|
||||||
Berry code can interact with DSL-generated objects by using the underscore suffix naming convention:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Code
|
|
||||||
animation pulse = pulsating_animation(color=red, period=2s)
|
|
||||||
berry """
|
|
||||||
pulse_.opacity = 200
|
|
||||||
pulse_.priority = 10
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Transpiles to Berry Code
|
|
||||||
var pulse_ = animation.pulsating_animation(engine)
|
|
||||||
pulse_.color = animation.red
|
|
||||||
pulse_.period = 2000
|
|
||||||
# Berry code block
|
|
||||||
pulse_.opacity = 200
|
|
||||||
pulse_.priority = 10
|
|
||||||
# End berry code block
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced DSL Features
|
|
||||||
|
|
||||||
### Templates
|
|
||||||
|
|
||||||
The DSL supports two types of templates: regular templates (functions) and template animations (classes).
|
|
||||||
|
|
||||||
#### Template Animation Transpilation
|
|
||||||
|
|
||||||
Template animations create reusable animation classes extending `engine_proxy`:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Template Animation
|
|
||||||
template animation shutter_effect {
|
|
||||||
param colors type palette nillable true
|
|
||||||
param duration type time min 0 max 3600 default 5 nillable false
|
|
||||||
|
|
||||||
set strip_len = strip_length()
|
|
||||||
color col = color_cycle(palette=colors, cycle_period=0)
|
|
||||||
|
|
||||||
animation shutter = beacon_animation(
|
|
||||||
color = col
|
|
||||||
beacon_size = strip_len / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
sequence seq repeat forever {
|
|
||||||
play shutter for duration
|
|
||||||
col.next = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
run seq
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Transpiles to:**
|
|
||||||
|
|
||||||
```berry
|
|
||||||
class shutter_effect_animation : animation.engine_proxy
|
|
||||||
static var PARAMS = animation.enc_params({
|
|
||||||
"colors": {"type": "palette", "nillable": true},
|
|
||||||
"duration": {"type": "time", "min": 0, "max": 3600, "default": 5, "nillable": false}
|
|
||||||
})
|
|
||||||
|
|
||||||
def init(engine)
|
|
||||||
super(self).init(engine)
|
|
||||||
|
|
||||||
var strip_len_ = animation.strip_length(engine)
|
|
||||||
var col_ = animation.color_cycle(engine)
|
|
||||||
col_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
|
|
||||||
col_.cycle_period = 0
|
|
||||||
|
|
||||||
var shutter_ = animation.beacon_animation(engine)
|
|
||||||
shutter_.color = col_
|
|
||||||
shutter_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 2 end)
|
|
||||||
|
|
||||||
var seq_ = animation.sequence_manager(engine, -1)
|
|
||||||
.push_play_step(shutter_, animation.resolve(self.duration))
|
|
||||||
.push_closure_step(def (engine) col_.next = 1 end)
|
|
||||||
|
|
||||||
self.add(seq_)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- Parameters accessed as `self.<param>` and wrapped in closures
|
|
||||||
- Constraints (min, max, default, nillable) encoded in PARAMS
|
|
||||||
- Uses `self.add()` instead of `engine.add()`
|
|
||||||
- Can be instantiated multiple times with different parameters
|
|
||||||
|
|
||||||
#### Regular Template Transpilation
|
|
||||||
|
|
||||||
Regular templates generate Berry functions:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# DSL Template
|
|
||||||
template pulse_effect {
|
|
||||||
param color type color
|
|
||||||
param speed
|
|
||||||
|
|
||||||
animation pulse = pulsating_animation(color=color, period=speed)
|
|
||||||
run pulse
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Transpiles to:**
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def pulse_effect_template(engine, color_, speed_)
|
|
||||||
var pulse_ = animation.pulsating_animation(engine)
|
|
||||||
pulse_.color = color_
|
|
||||||
pulse_.period = speed_
|
|
||||||
engine.add(pulse_)
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Template vs Template Animation
|
|
||||||
|
|
||||||
**Template Animation** (`template animation`):
|
|
||||||
- Generates classes extending `engine_proxy`
|
|
||||||
- Parameters accessed as `self.<param>`
|
|
||||||
- Supports parameter constraints (min, max, default, nillable)
|
|
||||||
- Uses `self.add()` for composition
|
|
||||||
- Can be instantiated multiple times
|
|
||||||
|
|
||||||
**Regular Template** (`template`):
|
|
||||||
- Generates functions
|
|
||||||
- Parameters accessed as `<param>_`
|
|
||||||
- Uses `engine.add()` for execution
|
|
||||||
- Called like functions
|
|
||||||
|
|
||||||
### User-Defined Functions
|
|
||||||
|
|
||||||
Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define custom function in Berry - engine must be first parameter
|
|
||||||
def custom_twinkle(engine, color, count, period)
|
|
||||||
var anim = animation.twinkle_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.count = count
|
|
||||||
atml:parameter>
|
|
||||||
</invoke>
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register the function for DSL use
|
|
||||||
animation.register_user_function("twinkle", custom_twinkle)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Use in DSL - engine is automatically passed as first argument
|
|
||||||
animation gold_twinkle = twinkle(0xFFD700, 8, 500ms)
|
|
||||||
animation blue_twinkle = twinkle(blue, 12, 300ms)
|
|
||||||
run gold_twinkle
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important**: The DSL transpiler automatically passes `engine` as the first argument to all user functions. Your function signature must include `engine` as the first parameter, but DSL users don't need to provide it when calling the function.
|
|
||||||
|
|
||||||
For comprehensive examples and best practices, see the **[User Functions Guide](USER_FUNCTIONS.md)**.
|
|
||||||
|
|
||||||
### Event System
|
|
||||||
|
|
||||||
Define event handlers that respond to triggers:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define animations for different states
|
|
||||||
color normal = 0x000080
|
|
||||||
color alert = 0xFF0000
|
|
||||||
|
|
||||||
animation normal_state = solid(color=normal)
|
|
||||||
animation alert_state = pulsating_animation(color=alert, period=500ms)
|
|
||||||
|
|
||||||
# Event handlers
|
|
||||||
on button_press {
|
|
||||||
run alert_state for 3s
|
|
||||||
run normal_state
|
|
||||||
}
|
|
||||||
|
|
||||||
on sensor_trigger {
|
|
||||||
run alert_state for 5s
|
|
||||||
wait 1s
|
|
||||||
run normal_state
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default state
|
|
||||||
run normal_state
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nested Function Calls
|
|
||||||
|
|
||||||
DSL supports nested function calls for complex compositions:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Nested calls in animation definitions (now supported)
|
|
||||||
animation complex = pulsating_animation(
|
|
||||||
color=red,
|
|
||||||
period=2s
|
|
||||||
)
|
|
||||||
|
|
||||||
# Nested calls in run statements
|
|
||||||
sequence demo {
|
|
||||||
play pulsating_animation(color=blue, period=1s) for 10s
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The DSL compiler validates classes and parameters at transpilation time, catching errors before execution:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
var invalid_dsl = "color red = #INVALID_COLOR\n"
|
|
||||||
"animation bad = unknown_function(red)\n"
|
|
||||||
"animation pulse = pulsating_animation(invalid_param=123)"
|
|
||||||
|
|
||||||
try
|
|
||||||
animation_dsl.execute(invalid_dsl)
|
|
||||||
except .. as e
|
|
||||||
print("DSL Error:", e)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transpilation-Time Validation
|
|
||||||
|
|
||||||
The DSL performs comprehensive validation during compilation:
|
|
||||||
|
|
||||||
**Animation Factory Validation:**
|
|
||||||
```berry
|
|
||||||
# Error: Function doesn't exist
|
|
||||||
animation bad = nonexistent_animation(color=red)
|
|
||||||
# Transpiler error: "Animation factory function 'nonexistent_animation' does not exist"
|
|
||||||
|
|
||||||
# Error: Function exists but doesn't create animation
|
|
||||||
animation bad2 = math_function(value=10)
|
|
||||||
# Transpiler error: "Function 'math_function' does not create an animation instance"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameter Validation:**
|
|
||||||
```berry
|
|
||||||
# Error: Invalid parameter name in constructor
|
|
||||||
animation pulse = pulsating_animation(invalid_param=123)
|
|
||||||
# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation"
|
|
||||||
|
|
||||||
# Error: Invalid parameter name in property assignment
|
|
||||||
animation pulse = pulsating_animation(color=red, period=2s)
|
|
||||||
pulse.wrong_arg = 15
|
|
||||||
# Transpiler error: "Animation 'PulseAnimation' does not have parameter 'wrong_arg'"
|
|
||||||
|
|
||||||
# Error: Parameter constraint violation
|
|
||||||
animation comet = comet_animation(tail_length=-5)
|
|
||||||
# Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Color Provider Validation:**
|
|
||||||
```berry
|
|
||||||
# Error: Color provider doesn't exist
|
|
||||||
color bad = nonexistent_color_provider(period=2s)
|
|
||||||
# Transpiler error: "Color provider factory 'nonexistent_color_provider' does not exist"
|
|
||||||
|
|
||||||
# Error: Function exists but doesn't create color provider
|
|
||||||
color bad2 = pulsating_animation(color=red)
|
|
||||||
# Transpiler error: "Function 'pulsating_animation' does not create a color provider instance"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Reference Validation:**
|
|
||||||
```berry
|
|
||||||
# Error: Undefined color reference
|
|
||||||
animation pulse = pulsating_animation(color=undefined_color)
|
|
||||||
# Transpiler error: "Undefined reference: 'undefined_color'"
|
|
||||||
|
|
||||||
# Error: Undefined animation reference in run statement
|
|
||||||
run nonexistent_animation
|
|
||||||
# Transpiler error: "Undefined reference 'nonexistent_animation' in run"
|
|
||||||
|
|
||||||
# Error: Undefined animation reference in sequence
|
|
||||||
sequence demo {
|
|
||||||
play nonexistent_animation for 5s
|
|
||||||
}
|
|
||||||
# Transpiler error: "Undefined reference 'nonexistent_animation' in sequence play"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Function Call Safety Validation:**
|
|
||||||
```berry
|
|
||||||
# Error: Dangerous function creation in computed expression
|
|
||||||
set strip_len3 = (strip_length() + 1) / 2
|
|
||||||
# Transpiler error: "Function 'strip_length()' cannot be used in computed expressions.
|
|
||||||
# This creates a new instance at each evaluation. Use either:
|
|
||||||
# set var_name = strip_length() # Single function call
|
|
||||||
# set computed = (existing_var + 1) / 2 # Computation with existing values"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why This Validation Exists:**
|
|
||||||
The transpiler prevents dangerous patterns where functions that create instances are called inside computed expressions that get wrapped in closures. This would create a new instance every time the closure is evaluated, leading to:
|
|
||||||
- Memory leaks
|
|
||||||
- Performance degradation
|
|
||||||
- Inconsistent behavior due to multiple timing states
|
|
||||||
|
|
||||||
**Safe Alternative:**
|
|
||||||
```berry
|
|
||||||
# ✅ CORRECT: Separate function call from computation
|
|
||||||
set strip_len = strip_length() # Single function call
|
|
||||||
set strip_len3 = (strip_len + 1) / 2 # Computation with existing value
|
|
||||||
```
|
|
||||||
|
|
||||||
**Template Parameter Validation:**
|
|
||||||
```berry
|
|
||||||
# Error: Duplicate parameter names
|
|
||||||
template bad_template {
|
|
||||||
param color type color
|
|
||||||
param color type number # Error: duplicate parameter name
|
|
||||||
}
|
|
||||||
# Transpiler error: "Duplicate parameter name 'color' in template"
|
|
||||||
|
|
||||||
# Error: Reserved keyword as parameter name
|
|
||||||
template reserved_template {
|
|
||||||
param animation type color # Error: conflicts with reserved keyword
|
|
||||||
}
|
|
||||||
# Transpiler error: "Parameter name 'animation' conflicts with reserved keyword"
|
|
||||||
|
|
||||||
# Error: Built-in color name as parameter
|
|
||||||
template color_template {
|
|
||||||
param red type number # Error: conflicts with built-in color
|
|
||||||
}
|
|
||||||
# Transpiler error: "Parameter name 'red' conflicts with built-in color name"
|
|
||||||
|
|
||||||
# Error: Invalid type annotation
|
|
||||||
template type_template {
|
|
||||||
param value type invalid_type # Error: invalid type
|
|
||||||
}
|
|
||||||
# Transpiler error: "Invalid parameter type 'invalid_type'. Valid types are: [...]"
|
|
||||||
|
|
||||||
# Warning: Unused parameter (compilation succeeds)
|
|
||||||
template unused_template {
|
|
||||||
param used_color type color
|
|
||||||
param unused_param type number # Warning: never used
|
|
||||||
|
|
||||||
animation test = solid(color=used_color)
|
|
||||||
run test
|
|
||||||
}
|
|
||||||
# Transpiler warning: "Template 'unused_template' parameter 'unused_param' is declared but never used"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Categories
|
|
||||||
|
|
||||||
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
|
|
||||||
- **Factory validation**: Non-existent or invalid animation/color provider factories
|
|
||||||
- **Parameter validation**: Invalid parameter names in constructors or property assignments
|
|
||||||
- **Template validation**: Invalid template parameter names, types, or usage patterns
|
|
||||||
- **Constraint validation**: Parameter values that violate defined constraints (min/max, enums, types)
|
|
||||||
- **Reference validation**: Using undefined colors, animations, or variables
|
|
||||||
- **Type validation**: Incorrect parameter types or incompatible assignments
|
|
||||||
- **Safety validation**: Dangerous patterns that could cause memory leaks or performance issues
|
|
||||||
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
|
|
||||||
|
|
||||||
### Warning Categories
|
|
||||||
|
|
||||||
The DSL transpiler also generates **warnings** that don't prevent compilation but indicate potential code quality issues:
|
|
||||||
|
|
||||||
- **Unused parameters**: Template parameters that are declared but never used in the template body
|
|
||||||
- **Code quality**: Suggestions for better coding practices
|
|
||||||
|
|
||||||
**Warning Behavior:**
|
|
||||||
- Warnings are included as comments in the generated Berry code
|
|
||||||
- Compilation succeeds even with warnings present
|
|
||||||
- Warnings help maintain code quality without being overly restrictive
|
|
||||||
|
|
||||||
## Performance Considerations
|
|
||||||
|
|
||||||
### DSL vs Programmatic Performance
|
|
||||||
|
|
||||||
- **DSL compilation overhead**: ~10-50ms depending on complexity
|
|
||||||
- **Generated code performance**: Identical to hand-written Berry code
|
|
||||||
- **Memory usage**: DSL compiler uses temporary memory during compilation
|
|
||||||
|
|
||||||
### Optimization Tips
|
|
||||||
|
|
||||||
1. **Compile once, run many times**:
|
|
||||||
```berry
|
|
||||||
var compiled = animation_dsl.compile(dsl_source)
|
|
||||||
var fn = compile(compiled)
|
|
||||||
|
|
||||||
# Run multiple times without recompilation
|
|
||||||
fn() # First execution
|
|
||||||
fn() # Subsequent executions are faster
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Use programmatic API for performance-critical code**:
|
|
||||||
```berry
|
|
||||||
# DSL for high-level structure
|
|
||||||
animation_dsl.execute(
|
|
||||||
"sequence main {\n"
|
|
||||||
"play performance_critical_anim for 10s\n"
|
|
||||||
"}\n"
|
|
||||||
"run main"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Programmatic for performance-critical animations
|
|
||||||
var performance_critical_anim = animation.create_optimized_animation()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration Examples
|
|
||||||
|
|
||||||
### With Tasmota Rules
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# In autoexec.be
|
|
||||||
import animation
|
|
||||||
import animation_dsl
|
|
||||||
|
|
||||||
def handle_rule_trigger(event)
|
|
||||||
if event == "motion"
|
|
||||||
animation_dsl.execute("color alert = 0xFF0000\n"
|
|
||||||
"animation alert_anim = pulsating_animation(color=alert, period=500ms)\n"
|
|
||||||
"run alert_anim for 5s")
|
|
||||||
elif event == "door"
|
|
||||||
animation_dsl.execute("color welcome = 0x00FF00\n"
|
|
||||||
"animation welcome_anim = breathe_animation(color=welcome, period=2s)\n"
|
|
||||||
"run welcome_anim for 8s")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register with Tasmota's rule system
|
|
||||||
tasmota.add_rule("motion", handle_rule_trigger)
|
|
||||||
```
|
|
||||||
|
|
||||||
### With Web Interface
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Create web endpoints for DSL execution
|
|
||||||
import webserver
|
|
||||||
|
|
||||||
def web_execute_dsl()
|
|
||||||
var dsl_code = webserver.arg("dsl")
|
|
||||||
if dsl_code
|
|
||||||
try
|
|
||||||
animation_dsl.execute(dsl_code)
|
|
||||||
webserver.content_response("DSL executed successfully")
|
|
||||||
except .. as e
|
|
||||||
webserver.content_response(f"DSL Error: {e}")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
webserver.content_response("No DSL code provided")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
webserver.on("/execute_dsl", web_execute_dsl)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Structure your DSL files**:
|
|
||||||
```berry
|
|
||||||
# Strip configuration first
|
|
||||||
strip length 60
|
|
||||||
|
|
||||||
# Colors next
|
|
||||||
color red = 0xFF0000
|
|
||||||
color blue = 0x0000FF
|
|
||||||
|
|
||||||
# Animations with named parameters
|
|
||||||
animation red_solid = solid(color=red)
|
|
||||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
|
||||||
|
|
||||||
# Property assignments
|
|
||||||
pulse_red.priority = 10
|
|
||||||
|
|
||||||
# Sequences
|
|
||||||
sequence demo {
|
|
||||||
play pulse_red for 5s
|
|
||||||
}
|
|
||||||
|
|
||||||
# Execution last
|
|
||||||
run demo
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Use meaningful names**:
|
|
||||||
```berry
|
|
||||||
# Good
|
|
||||||
color warning_red = 0xFF0000
|
|
||||||
animation door_alert = pulsating_animation(color=warning_red, period=500ms)
|
|
||||||
|
|
||||||
# Avoid
|
|
||||||
color c1 = 0xFF0000
|
|
||||||
animation a1 = pulsating_animation(color=c1, period=500ms)
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Comment your DSL**:
|
|
||||||
```berry
|
|
||||||
# Security system colors
|
|
||||||
color normal_blue = 0x000080 # Idle state
|
|
||||||
color alert_red = 0xFF0000 # Alert state
|
|
||||||
color success_green = 0x00FF00 # Success state
|
|
||||||
|
|
||||||
# Main security animation sequence
|
|
||||||
sequence security_demo {
|
|
||||||
play solid(color=normal_blue) for 10s # Normal operation
|
|
||||||
play pulsating_animation(color=alert_red, period=500ms) for 3s # Alert
|
|
||||||
play breathe_animation(color=success_green, period=2s) for 5s # Success confirmation
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Organize complex projects**:
|
|
||||||
```berry
|
|
||||||
# Load DSL modules
|
|
||||||
animation_dsl.load_file("colors.dsl") # Color definitions
|
|
||||||
animation_dsl.load_file("animations.dsl") # Animation library
|
|
||||||
animation_dsl.load_file("sequences.dsl") # Sequence definitions
|
|
||||||
animation_dsl.load_file("main.dsl") # Main execution
|
|
||||||
```
|
|
||||||
|
|
||||||
This completes the DSL reference documentation. The DSL provides a powerful, declarative way to create complex animations while maintaining the option to use the lightweight programmatic API when needed.
|
|
||||||
@ -1,479 +0,0 @@
|
|||||||
# Examples
|
|
||||||
|
|
||||||
Essential examples showcasing the Tasmota Berry Animation Framework using DSL syntax.
|
|
||||||
|
|
||||||
## Basic Animations
|
|
||||||
|
|
||||||
### 1. Solid Color
|
|
||||||
```berry
|
|
||||||
color red = 0xFF0000
|
|
||||||
animation red_solid = solid(color=red)
|
|
||||||
run red_solid
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Pulsing Effect
|
|
||||||
```berry
|
|
||||||
color blue = 0x0000FF
|
|
||||||
animation blue_pulse = pulsating_animation(color=blue, period=2s)
|
|
||||||
run blue_pulse
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Moving Comet
|
|
||||||
```berry
|
|
||||||
color cyan = 0x00FFFF
|
|
||||||
animation comet_trail = comet_animation(color=cyan, tail_length=8, speed=100ms, direction=1)
|
|
||||||
run comet_trail
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using Value Providers
|
|
||||||
|
|
||||||
### 4. Breathing Effect
|
|
||||||
```berry
|
|
||||||
set breathing = smooth(min_value=50, max_value=255, period=3s)
|
|
||||||
color white = 0xFFFFFF
|
|
||||||
animation breathing_white = solid(color=white)
|
|
||||||
breathing_white.opacity = breathing
|
|
||||||
run breathing_white
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Color Cycling
|
|
||||||
```berry
|
|
||||||
color rainbow = rainbow_color_provider(period=5s)
|
|
||||||
animation rainbow_cycle = solid(color=rainbow)
|
|
||||||
run rainbow_cycle
|
|
||||||
```
|
|
||||||
|
|
||||||
## Palette Animations
|
|
||||||
|
|
||||||
### 6. Fire Effect
|
|
||||||
```berry
|
|
||||||
palette fire_colors = [
|
|
||||||
(0, 0x000000), # Black
|
|
||||||
(128, 0xFF0000), # Red
|
|
||||||
(192, 0xFF8000), # Orange
|
|
||||||
(255, 0xFFFF00) # Yellow
|
|
||||||
]
|
|
||||||
|
|
||||||
animation fire_effect = palette_animation(palette=fire_colors, period=2s, intensity=255)
|
|
||||||
run fire_effect
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sequences
|
|
||||||
|
|
||||||
### 7. RGB Show
|
|
||||||
```berry
|
|
||||||
color red = 0xFF0000
|
|
||||||
color green = 0x00FF00
|
|
||||||
color blue = 0x0000FF
|
|
||||||
|
|
||||||
animation red_anim = solid(color=red)
|
|
||||||
animation green_anim = solid(color=green)
|
|
||||||
animation blue_anim = solid(color=blue)
|
|
||||||
|
|
||||||
sequence rgb_show {
|
|
||||||
play red_anim for 2s
|
|
||||||
play green_anim for 2s
|
|
||||||
play blue_anim for 2s
|
|
||||||
}
|
|
||||||
run rgb_show
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8. Sunrise Sequence
|
|
||||||
```berry
|
|
||||||
color deep_blue = 0x000080
|
|
||||||
color orange = 0xFFA500
|
|
||||||
color yellow = 0xFFFF00
|
|
||||||
|
|
||||||
animation night = solid(color=deep_blue)
|
|
||||||
animation sunrise = pulsating_animation(color=orange, period=3s)
|
|
||||||
animation day = solid(color=yellow)
|
|
||||||
|
|
||||||
sequence sunrise_show {
|
|
||||||
log("Starting sunrise sequence")
|
|
||||||
play night for 3s
|
|
||||||
log("Night phase complete, starting sunrise")
|
|
||||||
play sunrise for 5s
|
|
||||||
log("Sunrise complete, switching to day")
|
|
||||||
play day for 3s
|
|
||||||
log("Sunrise sequence finished")
|
|
||||||
}
|
|
||||||
run sunrise_show
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.1. Variable Duration Sequences
|
|
||||||
```berry
|
|
||||||
# Define timing variables for consistent durations
|
|
||||||
set short_duration = 2s
|
|
||||||
set long_duration = 5s
|
|
||||||
set fade_time = 1s
|
|
||||||
|
|
||||||
animation red_anim = solid(color=red)
|
|
||||||
animation green_anim = solid(color=green)
|
|
||||||
animation blue_anim = solid(color=blue)
|
|
||||||
|
|
||||||
sequence timed_show forever {
|
|
||||||
play red_anim for short_duration # Use variable duration
|
|
||||||
wait fade_time # Variable wait time
|
|
||||||
play green_anim for long_duration # Different variable duration
|
|
||||||
wait fade_time
|
|
||||||
play blue_anim for short_duration # Reuse timing variable
|
|
||||||
}
|
|
||||||
run timed_show
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sequence Assignments
|
|
||||||
|
|
||||||
### 9. Dynamic Property Changes
|
|
||||||
```berry
|
|
||||||
# Create oscillators for dynamic position
|
|
||||||
set triangle_val = triangle(min_value=0, max_value=27, duration=5s)
|
|
||||||
set cosine_val = cosine_osc(min_value=0, max_value=27, duration=5s)
|
|
||||||
|
|
||||||
# Create color cycle
|
|
||||||
palette eye_palette = [red, yellow, green, violet]
|
|
||||||
color eye_color = color_cycle(palette=eye_palette, cycle_period=0)
|
|
||||||
|
|
||||||
# Create beacon animation
|
|
||||||
animation red_eye = beacon_animation(
|
|
||||||
color=eye_color
|
|
||||||
pos=cosine_val
|
|
||||||
beacon_size=3
|
|
||||||
slew_size=2
|
|
||||||
priority=10
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sequence with property assignments
|
|
||||||
sequence cylon_eye {
|
|
||||||
play red_eye for 3s
|
|
||||||
red_eye.pos = triangle_val # Change to triangle oscillator
|
|
||||||
play red_eye for 3s
|
|
||||||
red_eye.pos = cosine_val # Change back to cosine
|
|
||||||
eye_color.next = 1 # Advance to next color
|
|
||||||
}
|
|
||||||
run cylon_eye
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10. Multiple Assignments in Sequence
|
|
||||||
```berry
|
|
||||||
set high_brightness = 255
|
|
||||||
set low_brightness = 64
|
|
||||||
color my_blue = 0x0000FF
|
|
||||||
|
|
||||||
animation test = solid(color=red)
|
|
||||||
test.opacity = high_brightness
|
|
||||||
|
|
||||||
sequence demo {
|
|
||||||
play test for 1s
|
|
||||||
test.opacity = low_brightness # Dim the animation
|
|
||||||
test.color = my_blue # Change color to blue
|
|
||||||
play test for 1s
|
|
||||||
test.opacity = high_brightness # Brighten again
|
|
||||||
play test for 1s
|
|
||||||
}
|
|
||||||
run demo
|
|
||||||
```
|
|
||||||
|
|
||||||
### 11. Restart in Sequences
|
|
||||||
```berry
|
|
||||||
# Create oscillator and animation
|
|
||||||
set wave_osc = triangle(min_value=0, max_value=29, period=4s)
|
|
||||||
animation wave = beacon_animation(color=blue, pos=wave_osc, beacon_size=5)
|
|
||||||
|
|
||||||
sequence sync_demo {
|
|
||||||
play wave for 3s
|
|
||||||
restart wave_osc # Restart oscillator time origin (if already started)
|
|
||||||
play wave for 3s # Wave starts from beginning again
|
|
||||||
restart wave # Restart animation time origin (if already started)
|
|
||||||
play wave for 3s
|
|
||||||
}
|
|
||||||
run sync_demo
|
|
||||||
```
|
|
||||||
|
|
||||||
### 12. Assignments in Repeat Blocks
|
|
||||||
```berry
|
|
||||||
set brightness = smooth(min_value=50, max_value=255, period=2s)
|
|
||||||
animation pulse = pulsating_animation(color=white, period=1s)
|
|
||||||
|
|
||||||
sequence breathing_cycle {
|
|
||||||
repeat 3 times {
|
|
||||||
play pulse for 500ms
|
|
||||||
pulse.opacity = brightness # Apply breathing effect
|
|
||||||
wait 200ms
|
|
||||||
pulse.opacity = 255 # Return to full brightness
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run breathing_cycle
|
|
||||||
```
|
|
||||||
|
|
||||||
## User Functions in Computed Parameters
|
|
||||||
|
|
||||||
### 13. Simple User Function
|
|
||||||
```berry
|
|
||||||
# Simple user function in computed parameter
|
|
||||||
animation random_base = solid(color=blue, priority=10)
|
|
||||||
random_base.opacity = rand_demo()
|
|
||||||
run random_base
|
|
||||||
```
|
|
||||||
|
|
||||||
### 14. User Function with Math Operations
|
|
||||||
```berry
|
|
||||||
# Mix user functions with mathematical functions
|
|
||||||
animation random_bounded = solid(
|
|
||||||
color=purple
|
|
||||||
opacity=max(50, min(255, rand_demo() + 100))
|
|
||||||
priority=15
|
|
||||||
)
|
|
||||||
run random_bounded
|
|
||||||
```
|
|
||||||
|
|
||||||
### 15. User Function in Arithmetic Expression
|
|
||||||
```berry
|
|
||||||
# Use user function in arithmetic expressions
|
|
||||||
animation random_variation = solid(
|
|
||||||
color=cyan
|
|
||||||
opacity=abs(rand_demo() - 128) + 64
|
|
||||||
priority=12
|
|
||||||
)
|
|
||||||
run random_variation
|
|
||||||
```
|
|
||||||
|
|
||||||
See `anim_examples/user_functions_demo.anim` for a complete working example.
|
|
||||||
|
|
||||||
## New Repeat System Examples
|
|
||||||
|
|
||||||
### 16. Runtime Repeat with Forever Loop
|
|
||||||
```berry
|
|
||||||
color red = 0xFF0000
|
|
||||||
color blue = 0x0000FF
|
|
||||||
animation red_anim = solid(color=red)
|
|
||||||
animation blue_anim = solid(color=blue)
|
|
||||||
|
|
||||||
# Traditional syntax with repeat sub-sequence
|
|
||||||
sequence cylon_effect {
|
|
||||||
repeat forever {
|
|
||||||
play red_anim for 1s
|
|
||||||
play blue_anim for 1s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Alternative syntax - sequence with repeat modifier
|
|
||||||
sequence cylon_effect_alt repeat forever {
|
|
||||||
play red_anim for 1s
|
|
||||||
play blue_anim for 1s
|
|
||||||
}
|
|
||||||
|
|
||||||
run cylon_effect
|
|
||||||
```
|
|
||||||
|
|
||||||
### 17. Nested Repeats (Multiplication)
|
|
||||||
```berry
|
|
||||||
color green = 0x00FF00
|
|
||||||
color yellow = 0xFFFF00
|
|
||||||
animation green_anim = solid(color=green)
|
|
||||||
animation yellow_anim = solid(color=yellow)
|
|
||||||
|
|
||||||
# Nested repeats: 3 × 2 = 6 total iterations
|
|
||||||
sequence nested_pattern {
|
|
||||||
repeat 3 times {
|
|
||||||
repeat 2 times {
|
|
||||||
play green_anim for 200ms
|
|
||||||
play yellow_anim for 200ms
|
|
||||||
}
|
|
||||||
wait 500ms # Pause between outer iterations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run nested_pattern
|
|
||||||
```
|
|
||||||
|
|
||||||
### 18. Repeat with Property Assignments
|
|
||||||
```berry
|
|
||||||
set triangle_pos = triangle(min_value=0, max_value=29, period=3s)
|
|
||||||
set cosine_pos = cosine_osc(min_value=0, max_value=29, period=3s)
|
|
||||||
|
|
||||||
color eye_color = color_cycle(palette=[red, yellow, green, blue], cycle_period=0)
|
|
||||||
animation moving_eye = beacon_animation(
|
|
||||||
color=eye_color
|
|
||||||
pos=triangle_pos
|
|
||||||
beacon_size=2
|
|
||||||
slew_size=1
|
|
||||||
)
|
|
||||||
|
|
||||||
sequence dynamic_cylon {
|
|
||||||
repeat 5 times {
|
|
||||||
play moving_eye for 2s
|
|
||||||
moving_eye.pos = cosine_pos # Switch to cosine movement
|
|
||||||
play moving_eye for 2s
|
|
||||||
moving_eye.pos = triangle_pos # Switch back to triangle
|
|
||||||
eye_color.next = 1 # Next color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run dynamic_cylon
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Examples
|
|
||||||
|
|
||||||
### 19. Dynamic Position
|
|
||||||
```berry
|
|
||||||
strip length 60
|
|
||||||
|
|
||||||
set moving_position = smooth(min_value=5, max_value=55, period=4s)
|
|
||||||
color purple = 0x8000FF
|
|
||||||
|
|
||||||
animation moving_pulse = beacon_animation(
|
|
||||||
color=purple,
|
|
||||||
position=moving_position,
|
|
||||||
beacon_size=3,
|
|
||||||
fade_size=2
|
|
||||||
)
|
|
||||||
run moving_pulse
|
|
||||||
```
|
|
||||||
|
|
||||||
### 20. Multi-Layer Effect
|
|
||||||
```berry
|
|
||||||
# Base layer - slow breathing
|
|
||||||
set breathing = smooth(min_value=100, max_value=255, period=4s)
|
|
||||||
color base_blue = 0x000080
|
|
||||||
animation base_layer = solid(color=base_blue)
|
|
||||||
base_layer.opacity = breathing
|
|
||||||
|
|
||||||
# Accent layer - twinkling stars
|
|
||||||
color star_white = 0xFFFFFF
|
|
||||||
animation stars = twinkle_animation(color=star_white, count=5, period=800ms)
|
|
||||||
stars.opacity = 150
|
|
||||||
|
|
||||||
sequence layered_effect {
|
|
||||||
play base_layer for 10s
|
|
||||||
play stars for 10s
|
|
||||||
}
|
|
||||||
run layered_effect
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tips for Creating Animations
|
|
||||||
|
|
||||||
### Start Simple
|
|
||||||
```berry
|
|
||||||
# Begin with basic colors and effects
|
|
||||||
color my_color = 0xFF0000
|
|
||||||
animation simple = solid(color=my_color)
|
|
||||||
run simple
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Meaningful Names
|
|
||||||
```berry
|
|
||||||
# Good - descriptive names
|
|
||||||
color sunset_orange = 0xFF8C00
|
|
||||||
animation evening_glow = pulsating_animation(color=sunset_orange, period=4s)
|
|
||||||
|
|
||||||
# Avoid - unclear names
|
|
||||||
color c1 = 0xFF8C00
|
|
||||||
animation a1 = pulsating_animation(color=c1, period=4s)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Incrementally
|
|
||||||
1. Start with solid colors
|
|
||||||
2. Add simple effects like pulse
|
|
||||||
3. Experiment with sequences
|
|
||||||
4. Combine multiple animations
|
|
||||||
|
|
||||||
### Performance Considerations
|
|
||||||
- Use sequences instead of multiple simultaneous animations
|
|
||||||
- Reuse value providers with the `set` keyword
|
|
||||||
- Keep animation periods reasonable (>500ms)
|
|
||||||
- Limit palette sizes for memory efficiency
|
|
||||||
|
|
||||||
## Template Examples
|
|
||||||
|
|
||||||
Templates provide reusable, parameterized animation patterns that promote code reuse and maintainability.
|
|
||||||
|
|
||||||
### 21. Simple Template
|
|
||||||
```berry
|
|
||||||
# Define a reusable blinking template
|
|
||||||
template blink_effect {
|
|
||||||
param color type color
|
|
||||||
param speed
|
|
||||||
param intensity
|
|
||||||
|
|
||||||
animation blink = pulsating_animation(
|
|
||||||
color=color
|
|
||||||
period=speed
|
|
||||||
)
|
|
||||||
blink.opacity = intensity
|
|
||||||
|
|
||||||
run blink
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use the template with different parameters
|
|
||||||
blink_effect(red, 1s, 80%)
|
|
||||||
blink_effect(blue, 500ms, 100%)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 22. Multi-Animation Template
|
|
||||||
```berry
|
|
||||||
# Template that creates a comet chase effect
|
|
||||||
template comet_chase {
|
|
||||||
param trail_color type color
|
|
||||||
param bg_color type color
|
|
||||||
param chase_speed
|
|
||||||
param tail_size
|
|
||||||
|
|
||||||
# Background layer
|
|
||||||
animation background = solid(color=bg_color)
|
|
||||||
background.priority = 1
|
|
||||||
|
|
||||||
# Comet effect layer
|
|
||||||
animation comet = comet_animation(
|
|
||||||
color=trail_color
|
|
||||||
tail_length=tail_size
|
|
||||||
speed=chase_speed
|
|
||||||
)
|
|
||||||
comet.priority = 10
|
|
||||||
|
|
||||||
run background
|
|
||||||
run comet
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create different comet effects
|
|
||||||
comet_chase(white, black, 1500ms, 8)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 23. Template with Dynamic Colors
|
|
||||||
```berry
|
|
||||||
# Template using color cycling and breathing effects
|
|
||||||
template breathing_rainbow {
|
|
||||||
param cycle_time
|
|
||||||
param breath_time
|
|
||||||
param base_brightness
|
|
||||||
|
|
||||||
# Create rainbow palette
|
|
||||||
palette rainbow = [
|
|
||||||
(0, red), (42, orange), (85, yellow)
|
|
||||||
(128, green), (170, blue), (213, purple), (255, red)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Create cycling rainbow color
|
|
||||||
color rainbow_cycle = color_cycle(
|
|
||||||
palette=rainbow
|
|
||||||
cycle_period=cycle_time
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create breathing animation with rainbow colors
|
|
||||||
animation breath = pulsating_animation(
|
|
||||||
color=rainbow_cycle
|
|
||||||
period=breath_time
|
|
||||||
)
|
|
||||||
breath.opacity = base_brightness
|
|
||||||
|
|
||||||
run breath
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use the rainbow breathing template
|
|
||||||
breathing_rainbow(5s, 2s, 200)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- **[DSL Reference](DSL_REFERENCE.md)** - Complete language syntax
|
|
||||||
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
|
|
||||||
- **[Animation Development](ANIMATION_DEVELOPMENT.md)** - Creating custom animations
|
|
||||||
|
|
||||||
Start with these examples and build your own amazing LED animations! 🎨✨
|
|
||||||
@ -1,263 +0,0 @@
|
|||||||
# Oscillation Patterns
|
|
||||||
|
|
||||||
Quick reference for oscillation patterns used with value providers in the Berry Animation Framework.
|
|
||||||
|
|
||||||
## Available Oscillation Patterns
|
|
||||||
|
|
||||||
These waveform constants can be used with `oscillator_value`:
|
|
||||||
|
|
||||||
| Constant | Value | Alias Functions | Behavior | Use Case |
|
|
||||||
|----------|-------|-----------------|----------|----------|
|
|
||||||
| `SAWTOOTH` | 1 | `linear`, `ramp` | Linear ramp up | Uniform motion |
|
|
||||||
| `TRIANGLE` | 2 | `triangle` | Linear up then down | Sharp direction changes |
|
|
||||||
| `SQUARE` | 3 | `square` | Alternating min/max | On/off effects |
|
|
||||||
| `COSINE` | 4 | `smooth` | Smooth cosine wave | Natural oscillation |
|
|
||||||
| `SINE` | 5 | `sine` | Pure sine wave | Classic wave motion |
|
|
||||||
| `EASE_IN` | 6 | `ease_in` | Slow start, fast end | Smooth acceleration |
|
|
||||||
| `EASE_OUT` | 7 | `ease_out` | Fast start, slow end | Smooth deceleration |
|
|
||||||
| `ELASTIC` | 8 | `elastic` | Spring overshoot | Bouncy effects |
|
|
||||||
| `BOUNCE` | 9 | `bounce` | Ball bouncing | Physics simulation |
|
|
||||||
|
|
||||||
## DSL Usage
|
|
||||||
|
|
||||||
### With Oscillator Value Provider
|
|
||||||
```berry
|
|
||||||
# Basic oscillator with different waveform types
|
|
||||||
set breathing = oscillator_value(min_value=50, max_value=255, duration=3000, form=COSINE)
|
|
||||||
set pulsing = ease_in(min_value=0, max_value=255, duration=2000)
|
|
||||||
set bouncing = oscillator_value(min_value=10, max_value=240, duration=4000, form=TRIANGLE)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Alias Functions
|
|
||||||
```berry
|
|
||||||
# These are equivalent to oscillator_value with specific forms
|
|
||||||
set smooth_fade = smooth(min_value=50, max_value=255, duration=3000) # form=COSINE
|
|
||||||
set sine_wave = sine_osc(min_value=50, max_value=255, duration=3000) # form=SINE
|
|
||||||
set cosine_wave = cosine_osc(min_value=50, max_value=255, duration=3000) # form=COSINE (alias for smooth)
|
|
||||||
set linear_sweep = linear(min_value=0, max_value=255, duration=2000) # form=SAWTOOTH
|
|
||||||
set triangle_wave = triangle(min_value=10, max_value=240, duration=4000) # form=TRIANGLE
|
|
||||||
```
|
|
||||||
|
|
||||||
### In Animations
|
|
||||||
```berry
|
|
||||||
color blue = 0x0000FF
|
|
||||||
set breathing = smooth(min_value=100, max_value=255, duration=4000)
|
|
||||||
|
|
||||||
animation breathing_blue = solid(color=blue)
|
|
||||||
breathing_blue.opacity = breathing
|
|
||||||
run breathing_blue
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pattern Characteristics
|
|
||||||
|
|
||||||
### SAWTOOTH (Linear)
|
|
||||||
- **Constant speed** throughout the cycle
|
|
||||||
- **Sharp reset** from max back to min
|
|
||||||
- **Best for**: Uniform sweeps, mechanical movements
|
|
||||||
|
|
||||||
```
|
|
||||||
Value
|
|
||||||
^
|
|
||||||
| /| /|
|
|
||||||
| / | / |
|
|
||||||
| / | / |
|
|
||||||
| / | / |
|
|
||||||
| / | / |
|
|
||||||
|/ |/ |
|
|
||||||
+------+------+----> Time
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set linear_brightness = linear(min_value=0, max_value=255, duration=2000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### COSINE (Smooth)
|
|
||||||
- **Gradual acceleration** and deceleration
|
|
||||||
- **Natural feeling** transitions
|
|
||||||
- **Best for**: Breathing effects, gentle fades
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set breathing_effect = smooth(min_value=50, max_value=255, duration=3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### SINE (Pure Wave)
|
|
||||||
- **Classic sine wave** starting from minimum
|
|
||||||
- **Smooth acceleration** and deceleration like cosine but phase-shifted
|
|
||||||
- **Best for**: Wave effects, classic oscillations, audio-visual sync
|
|
||||||
|
|
||||||
```
|
|
||||||
Value
|
|
||||||
^
|
|
||||||
| ___
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
|/ \___
|
|
||||||
+--------------------+----> Time
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set wave_motion = sine_osc(min_value=0, max_value=255, duration=2000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### TRIANGLE
|
|
||||||
- **Linear acceleration** to midpoint, then **linear deceleration**
|
|
||||||
- **Sharp direction changes** at extremes
|
|
||||||
- **Best for**: Bouncing effects, sharp transitions
|
|
||||||
|
|
||||||
```
|
|
||||||
Value
|
|
||||||
^
|
|
||||||
| /\
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
| / \
|
|
||||||
|/ \
|
|
||||||
+-------------+----> Time
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set bounce_position = triangle(min_value=5, max_value=55, duration=2000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### SQUARE
|
|
||||||
- **Alternating** between min and max values
|
|
||||||
- **Instant transitions** with configurable duty cycle
|
|
||||||
- **Best for**: On/off effects, strobing, digital patterns
|
|
||||||
|
|
||||||
```
|
|
||||||
Value
|
|
||||||
^
|
|
||||||
| +---+ +---+
|
|
||||||
| | | | |
|
|
||||||
| | | | |
|
|
||||||
| | +-----+ |
|
|
||||||
| | |
|
|
||||||
| | |
|
|
||||||
+-+-------------+----> Time
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set strobe_effect = square(min_value=0, max_value=255, duration=500, duty_cycle=25)
|
|
||||||
```
|
|
||||||
|
|
||||||
### EASE_IN
|
|
||||||
- **Slow start**, **fast finish**
|
|
||||||
- **Smooth acceleration** curve
|
|
||||||
- **Best for**: Starting animations, building intensity
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set accelerating = ease_in(min_value=0, max_value=255, duration=3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### EASE_OUT
|
|
||||||
- **Fast start**, **slow finish**
|
|
||||||
- **Smooth deceleration** curve
|
|
||||||
- **Best for**: Ending animations, gentle stops
|
|
||||||
|
|
||||||
```berry
|
|
||||||
set decelerating = ease_out(min_value=255, max_value=0, duration=3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Value Progression Examples
|
|
||||||
|
|
||||||
For a cycle from 0 to 100 over 2000ms:
|
|
||||||
|
|
||||||
| Time | SAWTOOTH | COSINE | SINE | TRIANGLE | EASE_IN | EASE_OUT |
|
|
||||||
|------|----------|--------|------|----------|---------|----------|
|
|
||||||
| 0ms | 0 | 0 | 0 | 0 | 0 | 0 |
|
|
||||||
| 500ms| 25 | 15 | 50 | 50 | 6 | 44 |
|
|
||||||
| 1000ms| 50 | 50 | 100 | 100 | 25 | 75 |
|
|
||||||
| 1500ms| 75 | 85 | 50 | 50 | 56 | 94 |
|
|
||||||
| 2000ms| 100 | 100 | 0 | 0 | 100 | 100 |
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Breathing Effect
|
|
||||||
```berry
|
|
||||||
color soft_white = 0xC0C0C0
|
|
||||||
set breathing = smooth(min_value=80, max_value=255, duration=4000)
|
|
||||||
|
|
||||||
animation breathing_light = solid(color=soft_white)
|
|
||||||
breathing_light.opacity = breathing
|
|
||||||
run breathing_light
|
|
||||||
```
|
|
||||||
|
|
||||||
### Position Sweep
|
|
||||||
```berry
|
|
||||||
strip length 60
|
|
||||||
color red = 0xFF0000
|
|
||||||
set sweeping_position = linear(min_value=0, max_value=59, duration=3000)
|
|
||||||
|
|
||||||
animation position_sweep = beacon_animation(
|
|
||||||
color=red,
|
|
||||||
position=sweeping_position,
|
|
||||||
beacon_size=3,
|
|
||||||
fade_size=1
|
|
||||||
)
|
|
||||||
run position_sweep
|
|
||||||
```
|
|
||||||
|
|
||||||
### Wave Motion
|
|
||||||
```berry
|
|
||||||
color purple = 0x8000FF
|
|
||||||
set wave_brightness = sine(min_value=50, max_value=255, duration=2500)
|
|
||||||
|
|
||||||
animation wave_effect = solid(color=purple)
|
|
||||||
wave_effect.opacity = wave_brightness
|
|
||||||
run wave_effect
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bouncing Effect
|
|
||||||
```berry
|
|
||||||
color green = 0x00FF00
|
|
||||||
set bounce_size = triangle(min_value=1, max_value=8, duration=1000)
|
|
||||||
|
|
||||||
animation bouncing_pulse = beacon_animation(
|
|
||||||
color=green,
|
|
||||||
position=30,
|
|
||||||
beacon_size=bounce_size,
|
|
||||||
fade_size=1
|
|
||||||
)
|
|
||||||
run bouncing_pulse
|
|
||||||
```
|
|
||||||
|
|
||||||
### Accelerating Fade
|
|
||||||
```berry
|
|
||||||
color blue = 0x0000FF
|
|
||||||
set fade_in = ease_in(min_value=0, max_value=255, duration=5000)
|
|
||||||
|
|
||||||
animation accelerating_fade = solid(color=blue)
|
|
||||||
accelerating_fade.opacity = fade_in
|
|
||||||
run accelerating_fade
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strobe Effect
|
|
||||||
```berry
|
|
||||||
color white = 0xFFFFFF
|
|
||||||
set strobe_pattern = square(min_value=0, max_value=255, duration=200, duty_cycle=10)
|
|
||||||
|
|
||||||
animation strobe_light = solid(color=white)
|
|
||||||
strobe_light.opacity = strobe_pattern
|
|
||||||
run strobe_light
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- **COSINE (smooth)**: Most natural for breathing and gentle effects
|
|
||||||
- **SINE**: Classic wave motion, perfect for audio-visual sync and pure oscillations
|
|
||||||
- **SAWTOOTH (linear)**: Best for consistent sweeps and mechanical movements
|
|
||||||
- **TRIANGLE**: Creates sharp, bouncing transitions
|
|
||||||
- **EASE_IN**: Perfect for building up intensity
|
|
||||||
- **EASE_OUT**: Ideal for gentle fade-outs
|
|
||||||
- **ELASTIC**: Spring-like effects with overshoot
|
|
||||||
- **BOUNCE**: Physics-based bouncing effects
|
|
||||||
- **SQUARE**: Good for on/off blinking effects
|
|
||||||
|
|
||||||
Choose the oscillation pattern that matches the feeling you want to create in your animation.
|
|
||||||
@ -1,276 +0,0 @@
|
|||||||
# Quick Start Guide
|
|
||||||
|
|
||||||
Get up and running with the Berry Animation Framework in 5 minutes using the DSL!
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Tasmota device with Berry support
|
|
||||||
- Addressable LED strip (WS2812, SK6812, etc.)
|
|
||||||
|
|
||||||
## Step 1: Your First Animation
|
|
||||||
|
|
||||||
Create a simple pulsing red light:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define colors
|
|
||||||
color bordeaux = 0x6F2C4F
|
|
||||||
|
|
||||||
# Create pulsing animation
|
|
||||||
animation pulse_bordeaux = pulsating_animation(color=bordeaux, period=3s)
|
|
||||||
|
|
||||||
# Run it
|
|
||||||
run pulse_bordeaux
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 2: Color Cycling
|
|
||||||
|
|
||||||
Create smooth color transitions:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Use predefined rainbow palette
|
|
||||||
animation rainbow_cycle = rich_palette(
|
|
||||||
palette=PALETTE_RAINBOW
|
|
||||||
cycle_period=5s
|
|
||||||
transition_type=1
|
|
||||||
)
|
|
||||||
|
|
||||||
run rainbow_cycle
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 3: Custom Palettes
|
|
||||||
|
|
||||||
Create your own color palettes:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define a sunset palette
|
|
||||||
palette sunset = [
|
|
||||||
(0, 0x191970) # Midnight blue
|
|
||||||
(64, purple) # Purple
|
|
||||||
(128, 0xFF69B4) # Hot pink
|
|
||||||
(192, orange) # Orange
|
|
||||||
(255, yellow) # Yellow
|
|
||||||
]
|
|
||||||
|
|
||||||
# Create palette animation
|
|
||||||
animation sunset_glow = rich_palette(
|
|
||||||
palette=sunset
|
|
||||||
cycle_period=8s
|
|
||||||
transition_type=1
|
|
||||||
)
|
|
||||||
|
|
||||||
run sunset_glow
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 4: Sequences
|
|
||||||
|
|
||||||
Create complex shows with sequences:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation red_pulse = pulsating_animation(color=red, period=2s)
|
|
||||||
animation green_pulse = pulsating_animation(color=green, period=2s)
|
|
||||||
animation blue_pulse = pulsating_animation(color=blue, period=2s)
|
|
||||||
|
|
||||||
sequence rgb_show {
|
|
||||||
play red_pulse for 3s
|
|
||||||
wait 500ms
|
|
||||||
play green_pulse for 3s
|
|
||||||
wait 500ms
|
|
||||||
play blue_pulse for 3s
|
|
||||||
|
|
||||||
repeat 2 times {
|
|
||||||
play red_pulse for 1s
|
|
||||||
play green_pulse for 1s
|
|
||||||
play blue_pulse for 1s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run rgb_show
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pro Tip: Variable Durations**
|
|
||||||
Use variables for consistent timing:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define timing variables
|
|
||||||
set short_time = 1s
|
|
||||||
set long_time = 3s
|
|
||||||
|
|
||||||
sequence timed_show {
|
|
||||||
play red_pulse for long_time # Use variable duration
|
|
||||||
wait 500ms
|
|
||||||
play green_pulse for short_time # Different timing
|
|
||||||
play blue_pulse for long_time # Reuse timing
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 5: Dynamic Effects
|
|
||||||
|
|
||||||
Add movement and variation to your animations:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Breathing effect with smooth oscillation
|
|
||||||
animation breathing = pulsating_animation(
|
|
||||||
color=blue
|
|
||||||
min_brightness=20%
|
|
||||||
max_brightness=100%
|
|
||||||
period=4s
|
|
||||||
)
|
|
||||||
|
|
||||||
# Moving comet effect
|
|
||||||
animation comet = comet_animation(
|
|
||||||
color=white
|
|
||||||
tail_length=8
|
|
||||||
speed=2000
|
|
||||||
)
|
|
||||||
|
|
||||||
# Twinkling effect
|
|
||||||
animation sparkles = twinkle_animation(
|
|
||||||
color=white
|
|
||||||
count=8
|
|
||||||
period=800ms
|
|
||||||
)
|
|
||||||
|
|
||||||
run breathing
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Fire Effect
|
|
||||||
```berry
|
|
||||||
animation fire = rich_palette(
|
|
||||||
palette=PALETTE_FIRE
|
|
||||||
cycle_period=2s
|
|
||||||
transition_type=1
|
|
||||||
)
|
|
||||||
|
|
||||||
run fire
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ocean Waves
|
|
||||||
```berry
|
|
||||||
animation ocean = rich_palette(
|
|
||||||
palette=PALETTE_OCEAN
|
|
||||||
cycle_period=6s
|
|
||||||
transition_type=1
|
|
||||||
)
|
|
||||||
|
|
||||||
run ocean
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tips for Success
|
|
||||||
|
|
||||||
1. **Start Simple** - Begin with solid colors and basic effects
|
|
||||||
2. **Use Predefined Palettes** - Try PALETTE_RAINBOW, PALETTE_FIRE, PALETTE_OCEAN
|
|
||||||
3. **Test Incrementally** - Add one animation at a time
|
|
||||||
4. **Use Named Colors** - red, blue, green, white, etc.
|
|
||||||
5. **Start with Longer Periods** - 3-5 seconds, then adjust as needed
|
|
||||||
|
|
||||||
## Loading DSL Files
|
|
||||||
|
|
||||||
Save your DSL code in `.anim` files and load them:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import animation
|
|
||||||
|
|
||||||
# Load DSL file
|
|
||||||
var runtime = animation.load_dsl_file("my_animation.anim")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Templates - Reusable Animation Patterns
|
|
||||||
|
|
||||||
### Template Animations
|
|
||||||
|
|
||||||
Template animations create reusable animation classes with parameters:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define a template animation with constraints
|
|
||||||
template animation shutter_effect {
|
|
||||||
param colors type palette nillable true
|
|
||||||
param duration type time min 0 max 3600 default 5 nillable false
|
|
||||||
|
|
||||||
set strip_len = strip_length()
|
|
||||||
color col = color_cycle(palette=colors, cycle_period=0)
|
|
||||||
|
|
||||||
animation shutter = beacon_animation(
|
|
||||||
color = col
|
|
||||||
beacon_size = strip_len / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
sequence seq repeat forever {
|
|
||||||
play shutter for duration
|
|
||||||
col.next = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
run seq
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create multiple instances with different parameters
|
|
||||||
palette rainbow = [red, orange, yellow, green, blue]
|
|
||||||
animation shutter1 = shutter_effect(colors=rainbow, duration=2s)
|
|
||||||
animation shutter2 = shutter_effect(colors=rainbow, duration=5s)
|
|
||||||
|
|
||||||
run shutter1
|
|
||||||
run shutter2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Template Animation Features:**
|
|
||||||
- **Reusable Classes** - Create multiple instances with different parameters
|
|
||||||
- **Parameter Constraints** - min, max, default, nillable values
|
|
||||||
- **Composition** - Combine multiple animations and sequences
|
|
||||||
- **Type Safe** - Parameter type checking
|
|
||||||
- **Implicit Parameters** - Automatically inherit parameters from base classes (name, priority, duration, loop, opacity, color, is_running)
|
|
||||||
|
|
||||||
### Regular Templates
|
|
||||||
|
|
||||||
Regular templates generate functions for simpler use cases:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
template pulse_effect {
|
|
||||||
param color type color
|
|
||||||
param speed
|
|
||||||
|
|
||||||
animation pulse = pulsating_animation(color=color, period=speed)
|
|
||||||
run pulse
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use the template
|
|
||||||
pulse_effect(red, 2s)
|
|
||||||
pulse_effect(blue, 1s)
|
|
||||||
```
|
|
||||||
|
|
||||||
## User-Defined Functions (Advanced)
|
|
||||||
|
|
||||||
For complex logic, create custom functions in Berry:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define custom function - engine must be first parameter
|
|
||||||
def my_twinkle(engine, color, count, period)
|
|
||||||
var anim = animation.twinkle_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.count = count
|
|
||||||
anim.period = period
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register for DSL use
|
|
||||||
animation.register_user_function("twinkle", my_twinkle)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Use in DSL - engine is automatically passed
|
|
||||||
animation gold_twinkles = twinkle(0xFFD700, 8, 500ms)
|
|
||||||
run gold_twinkles
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: The DSL automatically passes `engine` as the first argument to user functions.
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- **[DSL Reference](DSL_REFERENCE.md)** - Complete DSL syntax and features
|
|
||||||
- **[User Functions](USER_FUNCTIONS.md)** - Create custom animation functions
|
|
||||||
- **[Examples](EXAMPLES.md)** - More complex animation examples
|
|
||||||
- **[Animation Class Hierarchy](ANIMATION_CLASS_HIERARCHY.md)** - All available animations and parameters
|
|
||||||
- **[Oscillation Patterns](OSCILLATION_PATTERNS.md)** - Dynamic value patterns
|
|
||||||
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
|
|
||||||
|
|
||||||
Happy animating! 🎨✨
|
|
||||||
@ -1,858 +0,0 @@
|
|||||||
# DSL Transpiler Architecture
|
|
||||||
|
|
||||||
This document provides a detailed overview of the Berry Animation DSL transpiler architecture, including the core processing flow and expression processing chain.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The DSL transpiler (`transpiler.be`) converts Animation DSL code into executable Berry code. It uses a **ultra-simplified single-pass architecture** with comprehensive validation and code generation capabilities. The refactored transpiler emphasizes simplicity, robustness, and maintainability while providing extensive compile-time validation.
|
|
||||||
|
|
||||||
### Single-Pass Architecture Clarification
|
|
||||||
|
|
||||||
The transpiler is truly **single-pass** - it processes the token stream once from start to finish. When the documentation mentions "sequential steps" (like in template processing), these refer to **sequential operations within the single pass**, not separate passes over the data. For example:
|
|
||||||
|
|
||||||
- Template processing collects parameters, then collects body tokens **sequentially** in one pass
|
|
||||||
- Expression transformation handles mathematical functions, then user variables **sequentially** in one operation
|
|
||||||
- The transpiler never backtracks or re-processes the same tokens multiple times
|
|
||||||
|
|
||||||
## Core Processing Flow
|
|
||||||
|
|
||||||
The transpiler follows an **ultra-simplified single-pass architecture** with the following main flow:
|
|
||||||
|
|
||||||
```
|
|
||||||
transpile()
|
|
||||||
├── add("import animation")
|
|
||||||
├── while !at_end()
|
|
||||||
│ └── process_statement()
|
|
||||||
│ ├── Handle comments (preserve in output)
|
|
||||||
│ ├── Skip whitespace/newlines
|
|
||||||
│ ├── Auto-initialize strip if needed
|
|
||||||
│ ├── process_color()
|
|
||||||
│ │ ├── validate_user_name()
|
|
||||||
│ │ ├── _validate_color_provider_factory_exists()
|
|
||||||
│ │ └── _process_named_arguments_for_color_provider()
|
|
||||||
│ ├── process_palette()
|
|
||||||
│ │ ├── validate_user_name()
|
|
||||||
│ │ ├── Detect tuple vs alternative syntax
|
|
||||||
│ │ └── process_palette_color() (strict validation)
|
|
||||||
│ ├── process_animation()
|
|
||||||
│ │ ├── validate_user_name()
|
|
||||||
│ │ ├── _validate_animation_factory_creates_animation()
|
|
||||||
│ │ └── _process_named_arguments_for_animation()
|
|
||||||
│ ├── process_set()
|
|
||||||
│ │ ├── validate_user_name()
|
|
||||||
│ │ └── process_value()
|
|
||||||
│ ├── process_template()
|
|
||||||
│ │ ├── validate_user_name()
|
|
||||||
│ │ ├── Collect parameters with type annotations
|
|
||||||
│ │ ├── Collect body tokens
|
|
||||||
│ │ └── generate_template_function()
|
|
||||||
│ ├── process_sequence()
|
|
||||||
│ │ ├── validate_user_name()
|
|
||||||
│ │ ├── Parse repeat syntax (multiple variants)
|
|
||||||
│ │ └── process_sequence_statement() (fluent interface)
|
|
||||||
│ │ ├── process_play_statement_fluent()
|
|
||||||
│ │ ├── process_wait_statement_fluent()
|
|
||||||
│ │ ├── process_log_statement_fluent()
|
|
||||||
│ │ ├── process_restart_statement_fluent()
|
|
||||||
│ │ └── process_sequence_assignment_fluent()
|
|
||||||
│ ├── process_import() (direct Berry import generation)
|
|
||||||
│ ├── process_event_handler() (basic event system support)
|
|
||||||
│ ├── process_berry_code_block() (embed arbitrary Berry code)
|
|
||||||
│ ├── process_run() (collect for single engine.run())
|
|
||||||
│ └── process_property_assignment()
|
|
||||||
└── generate_engine_start() (single call for all run statements)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Statement Processing Details
|
|
||||||
|
|
||||||
#### Color Processing
|
|
||||||
```
|
|
||||||
process_color()
|
|
||||||
├── expect_identifier() → color name
|
|
||||||
├── validate_user_name() → check against reserved names
|
|
||||||
├── expect_assign() → '='
|
|
||||||
├── Check if function call (color provider)
|
|
||||||
│ ├── Check template_definitions first
|
|
||||||
│ ├── _validate_color_provider_factory_exists()
|
|
||||||
│ ├── add("var name_ = animation.func(engine)")
|
|
||||||
│ ├── Track in symbol_table for validation
|
|
||||||
│ └── _process_named_arguments_for_color_provider()
|
|
||||||
└── OR process_value() → static color value with symbol tracking
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Animation Processing
|
|
||||||
```
|
|
||||||
process_animation()
|
|
||||||
├── expect_identifier() → animation name
|
|
||||||
├── validate_user_name() → check against reserved names
|
|
||||||
├── expect_assign() → '='
|
|
||||||
├── Check if function call (animation factory)
|
|
||||||
│ ├── Check template_definitions first
|
|
||||||
│ ├── _validate_animation_factory_creates_animation()
|
|
||||||
│ ├── add("var name_ = animation.func(engine)")
|
|
||||||
│ ├── Track in symbol_table for validation
|
|
||||||
│ └── _process_named_arguments_for_animation()
|
|
||||||
└── OR process_value() → reference or literal with symbol tracking
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Sequence Processing (Enhanced)
|
|
||||||
```
|
|
||||||
process_sequence()
|
|
||||||
├── expect_identifier() → sequence name
|
|
||||||
├── validate_user_name() → check against reserved names
|
|
||||||
├── Track in sequence_names and symbol_table
|
|
||||||
├── Parse multiple repeat syntaxes:
|
|
||||||
│ ├── "sequence name repeat N times { ... }"
|
|
||||||
│ ├── "sequence name forever { ... }"
|
|
||||||
│ ├── "sequence name N times { ... }"
|
|
||||||
│ └── "sequence name { repeat ... }"
|
|
||||||
├── expect_left_brace() → '{'
|
|
||||||
├── add("var name_ = animation.sequence_manager(engine, repeat_count)")
|
|
||||||
├── while !check_right_brace()
|
|
||||||
│ └── process_sequence_statement() (fluent interface)
|
|
||||||
└── expect_right_brace() → '}'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Template Processing
|
|
||||||
```
|
|
||||||
process_template()
|
|
||||||
├── expect_identifier() → template name
|
|
||||||
├── validate_user_name() → check against reserved names
|
|
||||||
├── expect_left_brace() → '{'
|
|
||||||
├── Sequential step 1: collect parameters with type annotations
|
|
||||||
├── Sequential step 2: collect body tokens
|
|
||||||
├── expect_right_brace() → '}'
|
|
||||||
├── Store in template_definitions
|
|
||||||
├── generate_template_function()
|
|
||||||
│ ├── Create new transpiler instance for body
|
|
||||||
│ ├── Transpile body with fresh symbol table
|
|
||||||
│ ├── Generate Berry function with engine parameter
|
|
||||||
│ └── Register as user function
|
|
||||||
└── Track in symbol_table as "template"
|
|
||||||
|
|
||||||
process_template_animation()
|
|
||||||
├── expect_identifier() → template animation name
|
|
||||||
├── validate_user_name() → check against reserved names
|
|
||||||
├── expect_left_brace() → '{'
|
|
||||||
├── Sequential step 1: collect parameters with constraints (type, min, max, default)
|
|
||||||
├── Sequential step 2: collect body tokens
|
|
||||||
├── expect_right_brace() → '}'
|
|
||||||
├── generate_template_animation_class()
|
|
||||||
│ ├── Generate class extending engine_proxy
|
|
||||||
│ ├── Generate PARAMS with encode_constraints
|
|
||||||
│ ├── Create new transpiler instance for body
|
|
||||||
│ ├── Set template_animation_params for special handling
|
|
||||||
│ │ ├── Add user-defined parameters
|
|
||||||
│ │ └── Add inherited parameters from engine_proxy hierarchy (dynamic discovery)
|
|
||||||
│ ├── Transpile body with self.param references
|
|
||||||
│ └── Use self.add() instead of engine.add()
|
|
||||||
└── Track in symbol_table as "template"
|
|
||||||
|
|
||||||
### Implicit Parameters in Template Animations
|
|
||||||
|
|
||||||
Template animations automatically inherit parameters from the `engine_proxy` class hierarchy. The transpiler dynamically discovers these parameters at compile time:
|
|
||||||
|
|
||||||
**Dynamic Parameter Discovery:**
|
|
||||||
```
|
|
||||||
_add_inherited_params_to_template(template_params_map)
|
|
||||||
├── Create temporary engine_proxy instance
|
|
||||||
├── Walk up class hierarchy using introspection
|
|
||||||
├── For each class with PARAMS:
|
|
||||||
│ └── Add all parameter names to template_params_map
|
|
||||||
└── Fallback to static list if instance creation fails
|
|
||||||
```
|
|
||||||
|
|
||||||
**Inherited Parameters (from Animation and ParameterizedObject):**
|
|
||||||
- `id` (string, default: "animation")
|
|
||||||
- `priority` (int, default: 10)
|
|
||||||
- `duration` (int, default: 0)
|
|
||||||
- `loop` (bool, default: false)
|
|
||||||
- `opacity` (int, default: 255)
|
|
||||||
- `color` (int, default: 0)
|
|
||||||
- `is_running` (bool, default: false)
|
|
||||||
|
|
||||||
**Parameter Resolution Order:**
|
|
||||||
1. Check if identifier is in `template_animation_params` (includes both user-defined and inherited)
|
|
||||||
2. If found, resolve as `self.<param>` (template animation parameter)
|
|
||||||
3. Otherwise, check symbol table for user-defined variables
|
|
||||||
4. If not found, raise "Unknown identifier" error
|
|
||||||
|
|
||||||
This allows template animations to use inherited parameters like `duration` and `opacity` without explicit declaration, while still maintaining type safety and validation.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expression Processing Chain
|
|
||||||
|
|
||||||
The transpiler uses a **unified recursive descent parser** for expressions with **raw mode support** for closure contexts:
|
|
||||||
|
|
||||||
```
|
|
||||||
process_value(context)
|
|
||||||
└── process_additive_expression(context, is_top_level=true, raw_mode=false)
|
|
||||||
├── process_multiplicative_expression(context, is_top_level, raw_mode)
|
|
||||||
│ ├── process_unary_expression(context, is_top_level, raw_mode)
|
|
||||||
│ │ └── process_primary_expression(context, is_top_level, raw_mode)
|
|
||||||
│ │ ├── Parenthesized expression → recursive call
|
|
||||||
│ │ ├── Function call handling:
|
|
||||||
│ │ │ ├── Raw mode: mathematical functions → animation._math.method()
|
|
||||||
│ │ │ ├── Raw mode: template calls → template_func(self.engine, ...)
|
|
||||||
│ │ │ ├── Regular mode: process_function_call() or process_nested_function_call()
|
|
||||||
│ │ │ └── Simple function detection → _is_simple_function_call()
|
|
||||||
│ │ ├── Color literal → convert_color() (enhanced ARGB support)
|
|
||||||
│ │ ├── Time literal → process_time_value() (with variable support)
|
|
||||||
│ │ ├── Percentage → process_percentage_value()
|
|
||||||
│ │ ├── Number literal → return as-is
|
|
||||||
│ │ ├── String literal → quote and return
|
|
||||||
│ │ ├── Array literal → process_array_literal() (not in raw mode)
|
|
||||||
│ │ ├── Identifier → enhanced symbol resolution
|
|
||||||
│ │ │ ├── Object property → "obj.prop" with validation
|
|
||||||
│ │ │ ├── User function → _process_user_function_call()
|
|
||||||
│ │ │ ├── Palette constant → "animation.PALETTE_RAINBOW" etc.
|
|
||||||
│ │ │ ├── Named color → get_named_color_value()
|
|
||||||
│ │ │ └── Consolidated symbol resolution → resolve_symbol_reference()
|
|
||||||
│ │ └── Boolean keywords → true/false
|
|
||||||
│ └── Handle unary operators (-, +)
|
|
||||||
└── Handle multiplicative operators (*, /)
|
|
||||||
└── Handle additive operators (+, -)
|
|
||||||
└── Closure wrapping logic:
|
|
||||||
├── Skip in raw_mode
|
|
||||||
├── Special handling for repeat_count context
|
|
||||||
├── is_computed_expression_string() detection
|
|
||||||
└── create_computation_closure_from_string()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Expression Context Handling
|
|
||||||
|
|
||||||
The expression processor handles different contexts with **enhanced validation and processing**:
|
|
||||||
|
|
||||||
- **`"color"`** - Color definitions and assignments
|
|
||||||
- **`"animation"`** - Animation definitions and assignments
|
|
||||||
- **`"argument"`** - Function call arguments
|
|
||||||
- **`"property"`** - Property assignments with validation
|
|
||||||
- **`"variable"`** - Variable assignments with type tracking
|
|
||||||
- **`"repeat_count"`** - Sequence repeat counts (special closure handling)
|
|
||||||
- **`"time"`** - Time value processing with variable support
|
|
||||||
- **`"array_element"`** - Array literal elements
|
|
||||||
- **`"event_param"`** - Event handler parameters
|
|
||||||
- **`"expression"`** - Raw expression context (for closures)
|
|
||||||
|
|
||||||
### Computed Expression Detection (Enhanced)
|
|
||||||
|
|
||||||
The transpiler automatically detects computed expressions that need closures with **improved accuracy**:
|
|
||||||
|
|
||||||
```
|
|
||||||
is_computed_expression_string(expr_str)
|
|
||||||
├── Check for arithmetic operators (+, -, *, /) with spaces
|
|
||||||
├── Check for function calls (excluding simple functions)
|
|
||||||
│ ├── Extract function name before parenthesis
|
|
||||||
│ ├── Use _is_simple_function_call() to filter
|
|
||||||
│ └── Only mark complex functions as needing closures
|
|
||||||
├── Exclude simple parenthesized literals like (-1)
|
|
||||||
└── Return true only for actual computations
|
|
||||||
|
|
||||||
create_computation_closure_from_string(expr_str)
|
|
||||||
├── transform_expression_for_closure()
|
|
||||||
│ ├── Sequential step 1: Transform mathematical functions → animation._math.method()
|
|
||||||
│ │ ├── Use dynamic introspection with is_math_method()
|
|
||||||
│ │ ├── Check for existing "self." prefix /// TODO NOT SURE IT STILL EXISTS
|
|
||||||
│ │ └── Only transform if not already prefixed
|
|
||||||
│ ├── Sequential step 2: Transform user variables → animation.resolve(var_)
|
|
||||||
│ │ ├── Find variables ending with _
|
|
||||||
│ │ ├── Check for existing resolve() calls
|
|
||||||
│ │ ├── Avoid double-wrapping
|
|
||||||
│ │ └── Handle identifier character boundaries
|
|
||||||
│ └── Clean up extra spaces
|
|
||||||
└── Return "animation.create_closure_value(engine, closure)"
|
|
||||||
|
|
||||||
is_anonymous_function(expr_str)
|
|
||||||
├── Check if expression starts with "(def "
|
|
||||||
├── Check if expression ends with ")(engine)"
|
|
||||||
└── Skip closure wrapping for already-wrapped functions
|
|
||||||
```
|
|
||||||
|
|
||||||
## Enhanced Symbol Table System
|
|
||||||
|
|
||||||
The transpiler uses a sophisticated **SymbolTable** system for holistic symbol management and caching. This system provides dynamic symbol detection, type validation, and conflict prevention.
|
|
||||||
|
|
||||||
### SymbolTable Architecture
|
|
||||||
|
|
||||||
The symbol table consists of two main classes in `symbol_table.be`:
|
|
||||||
|
|
||||||
#### SymbolEntry Class
|
|
||||||
```
|
|
||||||
SymbolEntry
|
|
||||||
├── name: string # Symbol name
|
|
||||||
├── type: string # Symbol type classification
|
|
||||||
├── instance: object # Actual instance for validation
|
|
||||||
├── takes_args: boolean # Whether symbol accepts arguments
|
|
||||||
├── arg_type: string # "positional", "named", or "none"
|
|
||||||
└── is_builtin: boolean # Whether this is a built-in symbol from animation module
|
|
||||||
```
|
|
||||||
|
|
||||||
**Symbol Types Supported:**
|
|
||||||
- `"palette"` - Palette objects like `PALETTE_RAINBOW` (bytes instances)
|
|
||||||
- `"constant"` - Integer constants like `LINEAR`, `SINE`, `COSINE`
|
|
||||||
- `"math_function"` - Mathematical functions like `max`, `min`
|
|
||||||
- `"user_function"` - User-defined functions registered at runtime
|
|
||||||
- `"value_provider"` - Value provider constructors
|
|
||||||
- `"animation"` - Animation constructors
|
|
||||||
- `"color"` - Color definitions and providers
|
|
||||||
- `"variable"` - User-defined variables
|
|
||||||
- `"sequence"` - Sequence definitions
|
|
||||||
- `"template"` - Template definitions
|
|
||||||
|
|
||||||
#### SymbolTable Class
|
|
||||||
```
|
|
||||||
SymbolTable
|
|
||||||
├── entries: map # Map of name -> SymbolEntry
|
|
||||||
├── mock_engine: MockEngine # For validation testing
|
|
||||||
├── Dynamic Detection Methods:
|
|
||||||
│ ├── _detect_and_cache_symbol() # On-demand symbol detection
|
|
||||||
│ ├── contains() # Existence check with auto-detection
|
|
||||||
│ └── get() # Retrieval with auto-detection
|
|
||||||
├── Creation Methods:
|
|
||||||
│ ├── create_palette()
|
|
||||||
│ ├── create_color()
|
|
||||||
│ ├── create_animation()
|
|
||||||
│ ├── create_value_provider()
|
|
||||||
│ ├── create_variable()
|
|
||||||
│ ├── create_sequence()
|
|
||||||
│ └── create_template()
|
|
||||||
└── Validation Methods:
|
|
||||||
├── symbol_exists()
|
|
||||||
├── get_reference()
|
|
||||||
└── takes_args() / takes_positional_args() / takes_named_args()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dynamic Symbol Detection
|
|
||||||
|
|
||||||
The SymbolTable uses **lazy detection** to identify and cache symbols as they are encountered:
|
|
||||||
|
|
||||||
```
|
|
||||||
_detect_and_cache_symbol(name)
|
|
||||||
├── Check if already cached → return cached entry
|
|
||||||
├── Check animation module using introspection:
|
|
||||||
│ ├── Detect bytes() instances → create_palette()
|
|
||||||
│ ├── Detect integer constants (type == "int") → create_constant()
|
|
||||||
│ ├── Detect math functions in animation._math → create_math_function()
|
|
||||||
│ ├── Detect user functions via animation.is_user_function() → create_user_function()
|
|
||||||
│ ├── Test constructors with MockEngine:
|
|
||||||
│ │ ├── Create instance with mock_engine
|
|
||||||
│ │ ├── Check isinstance(instance, animation.value_provider) → create_value_provider()
|
|
||||||
│ │ └── Check isinstance(instance, animation.animation) → create_animation()
|
|
||||||
│ └── Cache result for future lookups
|
|
||||||
└── Return nil if not found (handled as user-defined)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Symbol Type Detection Examples
|
|
||||||
|
|
||||||
**Palette Detection:**
|
|
||||||
```berry
|
|
||||||
# DSL: animation rainbow = rich_palette_animation(palette=PALETTE_RAINBOW)
|
|
||||||
# Detection: PALETTE_RAINBOW exists in animation module, isinstance(obj, bytes)
|
|
||||||
# Result: SymbolEntry("PALETTE_RAINBOW", "palette", bytes_instance, true)
|
|
||||||
# Reference: "animation.PALETTE_RAINBOW"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Constant Detection:**
|
|
||||||
```berry
|
|
||||||
# DSL: animation wave = wave_animation(waveform=LINEAR)
|
|
||||||
# Detection: LINEAR exists in animation module, type(LINEAR) == "int"
|
|
||||||
# Result: SymbolEntry("LINEAR", "constant", 1, true)
|
|
||||||
# Reference: "animation.LINEAR"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Math Function Detection:**
|
|
||||||
```berry
|
|
||||||
# DSL: animation.opacity = max(100, min(255, brightness))
|
|
||||||
# Detection: max exists in animation._math, is callable
|
|
||||||
# Result: SymbolEntry("max", "math_function", nil, true)
|
|
||||||
# Reference: "animation.max" (transformed to "animation._math.max" in closures)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Value Provider Detection:**
|
|
||||||
```berry
|
|
||||||
# DSL: set oscillator = triangle(min_value=0, max_value=100, period=2s)
|
|
||||||
# Detection: triangle(mock_engine) creates instance, isinstance(instance, animation.value_provider)
|
|
||||||
# Result: SymbolEntry("triangle", "value_provider", instance, true)
|
|
||||||
# Reference: "animation.triangle"
|
|
||||||
```
|
|
||||||
|
|
||||||
**User Function Detection:**
|
|
||||||
```berry
|
|
||||||
# DSL: animation demo = rand_demo(color=red)
|
|
||||||
# Detection: animation.is_user_function("rand_demo") returns true
|
|
||||||
# Result: SymbolEntry("rand_demo", "user_function", nil, true)
|
|
||||||
# Reference: "rand_demo_" (handled specially in function calls)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Symbol Conflict Prevention
|
|
||||||
|
|
||||||
The SymbolTable prevents symbol redefinition conflicts:
|
|
||||||
|
|
||||||
```
|
|
||||||
add(name, entry)
|
|
||||||
├── Check for built-in symbol conflicts:
|
|
||||||
│ ├── _detect_and_cache_symbol(name)
|
|
||||||
│ └── Raise "symbol_redefinition_error" if types differ
|
|
||||||
├── Check existing user-defined symbols:
|
|
||||||
│ ├── Compare entry.type with existing.type
|
|
||||||
│ └── Raise "symbol_redefinition_error" if types differ
|
|
||||||
├── Allow same-type updates (reassignment)
|
|
||||||
└── Return entry for method chaining
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example Conflict Detection:**
|
|
||||||
```berry
|
|
||||||
# This would raise an error:
|
|
||||||
color max = 0xFF0000 # Conflicts with built-in math function "max"
|
|
||||||
|
|
||||||
# This would also raise an error:
|
|
||||||
color red = 0xFF0000
|
|
||||||
animation red = solid(color=blue) # Redefining "red" as different type
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration with Transpiler
|
|
||||||
|
|
||||||
The SymbolTable integrates seamlessly with the transpiler's processing flow:
|
|
||||||
|
|
||||||
### Performance Optimizations
|
|
||||||
|
|
||||||
**Caching Strategy:**
|
|
||||||
- **Lazy Detection**: Symbols detected only when first encountered
|
|
||||||
- **Instance Reuse**: MockEngine instances reused for validation
|
|
||||||
- **Introspection Caching**: Built-in symbol detection cached permanently
|
|
||||||
|
|
||||||
**Memory Efficiency:**
|
|
||||||
- **Minimal Storage**: Only essential information stored per symbol
|
|
||||||
- **Shared MockEngine**: Single MockEngine instance for all validation
|
|
||||||
- **Reference Counting**: Automatic cleanup of unused entries
|
|
||||||
|
|
||||||
### MockEngine Integration
|
|
||||||
|
|
||||||
The SymbolTable uses a lightweight MockEngine for constructor validation:
|
|
||||||
|
|
||||||
```
|
|
||||||
MockEngine
|
|
||||||
├── time_ms: 0 # Mock time for validation
|
|
||||||
├── get_strip_length(): 30 # Default strip length
|
|
||||||
└── Minimal interface for instance creation testing
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage in Detection:**
|
|
||||||
```berry
|
|
||||||
# Test if function creates value provider
|
|
||||||
try
|
|
||||||
var instance = factory_func(self.mock_engine)
|
|
||||||
if isinstance(instance, animation.value_provider)
|
|
||||||
return SymbolEntry.create_value_provider(name, instance, animation.value_provider)
|
|
||||||
end
|
|
||||||
except .. as e, msg
|
|
||||||
# Constructor failed - not a valid provider
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Validation System (Comprehensive)
|
|
||||||
|
|
||||||
The transpiler includes **extensive compile-time validation** with robust error handling:
|
|
||||||
|
|
||||||
### Factory Function Validation (Simplified using SymbolTable)
|
|
||||||
```
|
|
||||||
_validate_animation_factory_exists(func_name)
|
|
||||||
├── Skip validation for mathematical functions
|
|
||||||
├── Use symbol_table.get(func_name) for dynamic detection
|
|
||||||
└── Return true if entry exists (any callable function is valid)
|
|
||||||
|
|
||||||
_validate_animation_factory_creates_animation(func_name)
|
|
||||||
├── Use symbol_table.get(func_name) for dynamic detection
|
|
||||||
└── Return true if entry.type == "animation"
|
|
||||||
|
|
||||||
_validate_color_provider_factory_onsts(func_name)
|
|
||||||
├── Use symbol_table.get(func_name) for dynamic detection
|
|
||||||
└── Return true if entry exists (any callable function is valid)
|
|
||||||
|
|
||||||
_validate_value_provider_factory_exists(func_name)
|
|
||||||
├── Use symbol_table.get(func_name) for dynamic detection
|
|
||||||
└── Return true if entry.type == "value_provider"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameter Validation (Real-time)
|
|
||||||
```
|
|
||||||
_validate_single_parameter(func_name, param_name, animation_instance)
|
|
||||||
├── Use introspection to check if parameter exists
|
|
||||||
├── Call instance.has_param(param_name) for validation
|
|
||||||
├── Report detailed error messages with line numbers
|
|
||||||
├── Validate immediately as parameters are parsed
|
|
||||||
└── Graceful error handling to ensure transpiler robustness
|
|
||||||
|
|
||||||
_create_instance_for_validation(func_name) - Simplified using SymbolTable
|
|
||||||
├── Use symbol_table.get(func_name) for dynamic detection
|
|
||||||
└── Return entry.instance if available, nil otherwise
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reference Validation (Simplified using SymbolTable)
|
|
||||||
```
|
|
||||||
resolve_symbol_reference(name) - Simplified using SymbolTable
|
|
||||||
└── Use symbol_table.get_reference(name) for all symbol resolution
|
|
||||||
|
|
||||||
validate_symbol_reference(name, context) - With error reporting
|
|
||||||
├── Use symbol_exists() to check symbol_table
|
|
||||||
├── Report detailed error with context information
|
|
||||||
└── Return validation status
|
|
||||||
|
|
||||||
symbol_exists(name) - Simplified existence check
|
|
||||||
└── Use symbol_table.symbol_exists(name) for unified checking
|
|
||||||
|
|
||||||
_validate_value_provider_reference(object_name, context) - Simplified
|
|
||||||
├── Check symbol_exists() using symbol_table
|
|
||||||
├── Use symbol_table.get(name) for type information
|
|
||||||
├── Check entry.type == "value_provider" || entry.type == "animation"
|
|
||||||
└── Report detailed error messages for invalid types
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Name Validation (Reserved Names)
|
|
||||||
```
|
|
||||||
validate_user_name(name, definition_type)
|
|
||||||
├── Check against predefined color names
|
|
||||||
├── Check against DSL statement keywords
|
|
||||||
├── Report conflicts with suggestions for alternatives
|
|
||||||
└── Prevent redefinition of reserved identifiers
|
|
||||||
```
|
|
||||||
|
|
||||||
### Value Provider Validation (New)
|
|
||||||
```
|
|
||||||
_validate_value_provider_reference(object_name, context)
|
|
||||||
├── Check if symbol exists using validate_symbol_reference()
|
|
||||||
├── Check symbol_table markers for type information
|
|
||||||
├── Validate instance types using isinstance()
|
|
||||||
├── Ensure only value providers/animations can be restarted
|
|
||||||
└── Provide detailed error messages for invalid types
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Generation Patterns
|
|
||||||
|
|
||||||
### Engine-First Pattern (Consistent)
|
|
||||||
All factory functions use the engine-first pattern with **automatic strip initialization**:
|
|
||||||
```berry
|
|
||||||
# DSL: animation pulse = pulsating_animation(color=red, period=2s)
|
|
||||||
# Generated:
|
|
||||||
# Auto-generated strip initialization (using Tasmota configuration)
|
|
||||||
var engine = animation.init_strip()
|
|
||||||
|
|
||||||
var pulse_ = animation.pulsating_animation(engine)
|
|
||||||
pulse_.color = animation.red
|
|
||||||
pulse_.period = 2000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Template-Only Exception**: Files containing only template definitions skip engine initialization and `engine.run()` generation, producing pure function libraries.
|
|
||||||
|
|
||||||
### Symbol Resolution (Consolidated)
|
|
||||||
The transpiler resolves symbols at compile time using **unified resolution logic** based on the `is_builtin` flag:
|
|
||||||
```berry
|
|
||||||
# Built-in symbols (is_builtin=true) from animation module → animation.symbol
|
|
||||||
animation.linear, animation.PALETTE_RAINBOW, animation.SINE, animation.solid
|
|
||||||
|
|
||||||
# User-defined symbols (is_builtin=false) → symbol_
|
|
||||||
my_color_, my_animation_, my_sequence_
|
|
||||||
|
|
||||||
# Named colors → direct ARGB values (resolved at compile time)
|
|
||||||
red → 0xFFFF0000, blue → 0xFF0000FF
|
|
||||||
|
|
||||||
# Template calls → template_function(engine, args)
|
|
||||||
my_template(red, 2s) → my_template_template(engine, 0xFFFF0000, 2000)
|
|
||||||
|
|
||||||
|
|
||||||
### Closure Generation (Enhanced)
|
|
||||||
Dynamic expressions are wrapped in closures with **mathematical function support**:
|
|
||||||
```berry
|
|
||||||
# DSL: animation.opacity = strip_length() / 2 + 50
|
|
||||||
# Generated:
|
|
||||||
animation.opacity = animation.create_closure_value(engine,
|
|
||||||
def (self) return animation.resolve(strip_length_(engine)) / 2 + 50 end)
|
|
||||||
|
|
||||||
# DSL: animation.opacity = max(100, min(255, rand_demo() + 50))
|
|
||||||
# Generated:
|
|
||||||
animation.opacity = animation.create_closure_value(engine,
|
|
||||||
def (self) return animation._math.max(100, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 50)) end)
|
|
||||||
|
|
||||||
# Mathematical functions are automatically detected and prefixed with animation._math.
|
|
||||||
# User functions are wrapped with animation.get_user_function() calls
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Generation (New)
|
|
||||||
Templates are transpiled into Berry functions and registered as user functions:
|
|
||||||
```berry
|
|
||||||
# DSL Template:
|
|
||||||
template pulse_effect {
|
|
||||||
param color type color
|
|
||||||
param speed
|
|
||||||
|
|
||||||
animation pulse = pulsating_animation(color=color, period=speed)
|
|
||||||
run pulse
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generated:
|
|
||||||
def pulse_effect_template(engine, color_, speed_)
|
|
||||||
var pulse_ = animation.pulsating_animation(engine)
|
|
||||||
pulse_.color = color_
|
|
||||||
pulse_.period = speed_
|
|
||||||
engine.add(pulse_)
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sequence Generation (Fluent Interface)
|
|
||||||
Sequences use fluent interface pattern for better readability:
|
|
||||||
```berry
|
|
||||||
# DSL: sequence demo { play anim for 2s; wait 1s }
|
|
||||||
# Generated:
|
|
||||||
var demo_ = animation.sequence_manager(engine)
|
|
||||||
.push_play_step(anim_, 2000)
|
|
||||||
.push_wait_step(1000)
|
|
||||||
|
|
||||||
# Nested repeats use sub-sequences:
|
|
||||||
var demo_ = animation.sequence_manager(engine)
|
|
||||||
.push_repeat_subsequence(animation.sequence_manager(engine, 3)
|
|
||||||
.push_play_step(anim_, 1000)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Template System (Enhanced)
|
|
||||||
|
|
||||||
Templates are transpiled into Berry functions with **comprehensive parameter handling**:
|
|
||||||
|
|
||||||
**Template-Only Optimization**: Files containing only template definitions skip engine initialization and execution code generation, producing pure Berry function libraries.
|
|
||||||
|
|
||||||
```
|
|
||||||
process_template()
|
|
||||||
├── expect_identifier() → template name
|
|
||||||
├── validate_user_name() → check against reserved names
|
|
||||||
├── expect_left_brace() → '{'
|
|
||||||
├── Sequential step 1: collect parameters with type annotations
|
|
||||||
│ ├── Parse "param name type annotation" syntax
|
|
||||||
│ ├── Store parameter names and optional types
|
|
||||||
│ └── Support both typed and untyped parameters
|
|
||||||
├── Sequential step 2: collect body tokens until closing brace
|
|
||||||
│ ├── Handle nested braces correctly
|
|
||||||
│ ├── Preserve all tokens for later transpilation
|
|
||||||
│ └── Track brace depth for proper parsing
|
|
||||||
├── expect_right_brace() → '}'
|
|
||||||
├── Store in template_definitions for call resolution
|
|
||||||
├── generate_template_function()
|
|
||||||
│ ├── Create new SimpleDSLTranspiler instance for body
|
|
||||||
│ ├── Set up fresh symbol table with parameters
|
|
||||||
│ ├── Mark strip as initialized (templates assume engine exists)
|
|
||||||
│ ├── Transpile body using transpile_template_body()
|
|
||||||
│ ├── Generate Berry function with engine + parameters
|
|
||||||
│ ├── Handle transpilation errors gracefully
|
|
||||||
│ └── Register as user function automatically
|
|
||||||
└── Track in symbol_table as "template"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Call Resolution (Multiple Contexts)
|
|
||||||
```berry
|
|
||||||
# DSL template call in animation context:
|
|
||||||
animation my_anim = my_template(red, 2s)
|
|
||||||
# Generated: var my_anim_ = my_template_template(engine, 0xFFFF0000, 2000)
|
|
||||||
|
|
||||||
# DSL template call in property context:
|
|
||||||
animation.opacity = my_template(blue, 1s)
|
|
||||||
# Generated: animation.opacity = my_template_template(self.engine, 0xFF0000FF, 1000)
|
|
||||||
|
|
||||||
# DSL standalone template call:
|
|
||||||
my_template(green, 3s)
|
|
||||||
# Generated: my_template_template(engine, 0xFF008000, 3000)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Body Transpilation
|
|
||||||
Templates use a **separate transpiler instance** with isolated symbol table:
|
|
||||||
- Fresh symbol table prevents name conflicts
|
|
||||||
- Parameters are added as "parameter" markers
|
|
||||||
- Run statements are processed immediately (not collected)
|
|
||||||
- Template calls can be nested (templates calling other templates)
|
|
||||||
- Error handling preserves context information
|
|
||||||
|
|
||||||
## Error Handling (Robust)
|
|
||||||
|
|
||||||
The transpiler provides **comprehensive error reporting** with graceful degradation:
|
|
||||||
|
|
||||||
### Error Categories
|
|
||||||
- **Syntax errors** - Invalid DSL syntax with line numbers
|
|
||||||
- **Factory validation** - Non-existent animation/color factories with suggestions
|
|
||||||
- **Parameter validation** - Invalid parameter names with class context
|
|
||||||
- **Reference validation** - Undefined object references with context information
|
|
||||||
- **Constraint validation** - Parameter values outside valid ranges
|
|
||||||
- **Type validation** - Incorrect parameter types with expected types
|
|
||||||
- **Safety validation** - Dangerous patterns that could cause memory leaks or performance issues
|
|
||||||
- **Template errors** - Template definition and call validation
|
|
||||||
- **Reserved name conflicts** - User names conflicting with built-ins
|
|
||||||
|
|
||||||
### Error Reporting Features
|
|
||||||
```berry
|
|
||||||
error(msg)
|
|
||||||
├── Capture current line number from token
|
|
||||||
├── Format error with context: "Line X: message"
|
|
||||||
├── Store in errors array for batch reporting
|
|
||||||
└── Continue transpilation for additional error discovery
|
|
||||||
|
|
||||||
get_error_report()
|
|
||||||
├── Check if errors exist
|
|
||||||
├── Format comprehensive error report
|
|
||||||
├── Include all errors with line numbers
|
|
||||||
└── Provide user-friendly error messages
|
|
||||||
```
|
|
||||||
|
|
||||||
### Graceful Error Handling
|
|
||||||
- **Try-catch blocks** around validation to prevent crashes
|
|
||||||
- **Robust validation** that continues on individual failures
|
|
||||||
- **Skip statement** functionality to recover from parse errors
|
|
||||||
- **Default values** when validation fails to maintain transpilation flow
|
|
||||||
- **Context preservation** in error messages for better debugging
|
|
||||||
|
|
||||||
## Performance Considerations
|
|
||||||
|
|
||||||
### Ultra-Simplified Architecture
|
|
||||||
- **Single-pass processing** - tokens processed once from start to finish
|
|
||||||
- **Incremental symbol table** - builds validation context as it parses
|
|
||||||
- **Immediate validation** - catches errors as soon as they're encountered
|
|
||||||
- **Minimal state tracking** - only essential information is maintained
|
|
||||||
|
|
||||||
### Compile-Time Optimization
|
|
||||||
- **Symbol resolution at transpile time** - eliminates runtime lookups
|
|
||||||
- **Parameter validation during parsing** - catches errors early
|
|
||||||
- **Template pre-compilation** - templates become efficient Berry functions
|
|
||||||
- **Closure detection** - only wraps expressions that actually need it
|
|
||||||
- **Mathematical function detection** - uses dynamic introspection for accuracy
|
|
||||||
|
|
||||||
### Memory Efficiency
|
|
||||||
- **Streaming token processing** - no large intermediate AST structures
|
|
||||||
- **Direct code generation** - output generated as parsing proceeds
|
|
||||||
- **Minimal intermediate representations** - tokens and symbol table only
|
|
||||||
- **Template isolation** - separate transpiler instances prevent memory leaks
|
|
||||||
- **Graceful error handling** - prevents memory issues from validation failures
|
|
||||||
|
|
||||||
### Validation Efficiency
|
|
||||||
- **MockEngine pattern** - lightweight validation without full engine
|
|
||||||
- **Introspection caching** - validation results can be cached
|
|
||||||
- **Early termination** - stops processing invalid constructs quickly
|
|
||||||
- **Batch error reporting** - collects multiple errors in single pass
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
### Animation Module Integration
|
|
||||||
- **Factory function discovery** via introspection with existence checking
|
|
||||||
- **Parameter validation** using instance methods and has_param()
|
|
||||||
- **Symbol resolution** using module contents with fallback handling
|
|
||||||
- **Mathematical function detection** using dynamic introspection of ClosureValueProvider
|
|
||||||
- **Automatic strip initialization** when no explicit strip configuration
|
|
||||||
|
|
||||||
### User Function Integration
|
|
||||||
- **Template registration** as user functions with automatic naming
|
|
||||||
- **User function call detection** usable as normal functions with positional arguments
|
|
||||||
- **Closure generation** for computed parameters with mathematical functions
|
|
||||||
- **Template call resolution** in multiple contexts (animation, property, standalone)
|
|
||||||
- **Import statement processing** for user function modules
|
|
||||||
|
|
||||||
### DSL Language Integration
|
|
||||||
- **Comment preservation** in generated Berry code
|
|
||||||
- **Inline comment handling** with proper spacing
|
|
||||||
- **Multiple syntax support** for sequences (repeat variants)
|
|
||||||
- **Palette syntax flexibility** (tuple vs alternative syntax)
|
|
||||||
- **Time unit conversion** with variable support
|
|
||||||
- **Percentage conversion** to 0-255 range
|
|
||||||
|
|
||||||
### Robustness Features
|
|
||||||
- **Graceful error recovery** - continues parsing after errors
|
|
||||||
- **Validation isolation** - validation failures don't crash transpiler
|
|
||||||
- **Symbol table tracking** - maintains context for validation
|
|
||||||
- **Template isolation** - separate transpiler instances prevent conflicts
|
|
||||||
- **Reserved name protection** - prevents conflicts with built-in identifiers
|
|
||||||
|
|
||||||
## Key Architectural Changes
|
|
||||||
|
|
||||||
The refactored transpiler emphasizes:
|
|
||||||
|
|
||||||
1. **Simplicity** - Ultra-simplified single-pass architecture
|
|
||||||
2. **Robustness** - Comprehensive error handling and graceful degradation
|
|
||||||
3. **Enhanced Symbol Management** - Dynamic SymbolTable system with intelligent caching and conflict detection
|
|
||||||
4. **Validation** - Extensive compile-time validation with detailed error messages
|
|
||||||
5. **Flexibility** - Support for templates, multiple syntax variants, and user functions
|
|
||||||
6. **Performance** - Efficient processing with minimal memory overhead and lazy symbol detection
|
|
||||||
7. **Maintainability** - Clear separation of concerns and unified processing methods
|
|
||||||
|
|
||||||
## Recent Refactoring Improvements
|
|
||||||
|
|
||||||
### Code Simplification Using SymbolTable
|
|
||||||
|
|
||||||
The transpiler has been significantly refactored to leverage the `symbol_table.be` system more extensively:
|
|
||||||
|
|
||||||
#### **Factory Validation Simplification**
|
|
||||||
- **Before**: Complex validation with introspection and manual instance creation (~50 lines)
|
|
||||||
- **After**: Simple validation using symbol_table's dynamic detection (~25 lines)
|
|
||||||
- **Improvement**: 50% code reduction with better maintainability
|
|
||||||
|
|
||||||
#### **Symbol Resolution Consolidation**
|
|
||||||
- **Before**: Multiple separate checks for sequences, introspection, etc.
|
|
||||||
- **After**: Unified resolution through `symbol_table.get_reference()`
|
|
||||||
- **Improvement**: Single source of truth for all symbol resolution
|
|
||||||
|
|
||||||
#### **Duplicate Code Elimination**
|
|
||||||
- **Before**: Duplicate code patterns in `process_color()` and `process_animation()` methods
|
|
||||||
- **After**: Consolidated into reusable `_process_simple_value_assignment()` helper
|
|
||||||
- **Improvement**: 70% reduction in duplicate code blocks
|
|
||||||
|
|
||||||
#### **Legacy Variable Removal**
|
|
||||||
- **Before**: Separate tracking of sequences in `sequence_names` variable
|
|
||||||
- **After**: All symbols tracked uniformly in `symbol_table`
|
|
||||||
- **Improvement**: Eliminated redundancy and simplified state management
|
|
||||||
|
|
||||||
### Major Enhancements
|
|
||||||
|
|
||||||
**SymbolTable System:**
|
|
||||||
- **Dynamic Detection**: Automatically detects and caches symbol types as encountered
|
|
||||||
- **Conflict Prevention**: Prevents redefinition of symbols with different types
|
|
||||||
- **Performance Optimization**: Lazy loading and efficient symbol resolution for optimal performance
|
|
||||||
- **Type Safety**: Comprehensive type checking with MockEngine validation
|
|
||||||
- **Modular Design**: Separated into `symbol_table.be` for reusability
|
|
||||||
- **Constant Detection**: Added support for integer constants like `LINEAR`, `SINE`, `COSINE`
|
|
||||||
|
|
||||||
**Enhanced Symbol Detection:**
|
|
||||||
- **Palette Objects**: `PALETTE_RAINBOW` → `animation.PALETTE_RAINBOW`
|
|
||||||
- **Integer Constants**: `LINEAR`, `SINE`, `COSINE` → `animation.LINEAR`, `animation.SINE`, `animation.COSINE`
|
|
||||||
- **Math Functions**: `max`, `min` → `animation.max`, `animation.min` (transformed to `animation._math.*` in closures)
|
|
||||||
- **Value Providers**: `triangle`, `smooth` → `animation.triangle`, `animation.smooth`
|
|
||||||
- **Animation Constructors**: `solid`, `pulsating_animation` → `animation.solid`, `animation.pulsating_animation`
|
|
||||||
- **User-defined Symbols**: `my_color`, `my_animation` → `my_color_`, `my_animation_`
|
|
||||||
|
|
||||||
**Validation Improvements:**
|
|
||||||
- **Real-time Validation**: Parameter validation as symbols are parsed
|
|
||||||
- **Instance-based Checking**: Uses actual instances for accurate validation
|
|
||||||
- **Graceful Error Handling**: Robust error recovery with detailed error messages
|
|
||||||
- **Simplified Validation Methods**: Factory validation reduced from ~50 to ~25 lines using symbol_table
|
|
||||||
- **Unified Symbol Checking**: All symbol existence checks go through symbol_table system
|
|
||||||
- **Enhanced Type Detection**: Automatic detection of constants, palettes, functions, and constructors
|
|
||||||
|
|
||||||
This architecture ensures robust, efficient transpilation from DSL to executable Berry code while providing comprehensive validation, detailed error reporting, intelligent symbol management, and extensive language features.
|
|
||||||
|
|
||||||
### Symbol Reference Generation
|
|
||||||
|
|
||||||
The enhanced SymbolEntry system uses the `is_builtin` flag to determine correct reference generation:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# SymbolEntry.get_reference() method
|
|
||||||
def get_reference()
|
|
||||||
if self.is_builtin
|
|
||||||
return f"animation.{self.name}" # Built-in symbols: animation.LINEAR
|
|
||||||
else
|
|
||||||
return f"{self.name}_" # User-defined symbols: my_color_
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
- **Built-in Constants**: `LINEAR` → `animation.LINEAR`
|
|
||||||
- **Built-in Functions**: `triangle` → `animation.triangle`
|
|
||||||
- **Built-in Palettes**: `PALETTE_RAINBOW` → `animation.PALETTE_RAINBOW`
|
|
||||||
- **User-defined Colors**: `my_red` → `my_red_`
|
|
||||||
- **User-defined Animations**: `pulse_anim` → `pulse_anim_`
|
|
||||||
|
|
||||||
This ensures consistent and correct symbol resolution throughout the transpilation process.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,677 +0,0 @@
|
|||||||
# User-Defined Functions
|
|
||||||
|
|
||||||
Create custom animation functions in Berry and use them seamlessly in the Animation DSL.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### 1. Create Your Function
|
|
||||||
|
|
||||||
Write a Berry function that creates and returns an animation:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Define a custom breathing effect
|
|
||||||
def my_breathing(engine, color, speed)
|
|
||||||
var anim = animation.pulsating_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.min_brightness = 50
|
|
||||||
anim.max_brightness = 255
|
|
||||||
anim.period = speed
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Register It
|
|
||||||
|
|
||||||
Make your function available in DSL:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation.register_user_function("breathing", my_breathing)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Use It in DSL
|
|
||||||
|
|
||||||
First, import your user functions module, then call your function directly in computed parameters:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Import your user functions module
|
|
||||||
import user_functions
|
|
||||||
|
|
||||||
# Use your custom function in computed parameters
|
|
||||||
animation calm = solid(color=blue)
|
|
||||||
calm.opacity = breathing_effect()
|
|
||||||
|
|
||||||
animation energetic = solid(color=red)
|
|
||||||
energetic.opacity = breathing_effect()
|
|
||||||
|
|
||||||
sequence demo {
|
|
||||||
play calm for 10s
|
|
||||||
play energetic for 5s
|
|
||||||
}
|
|
||||||
|
|
||||||
run demo
|
|
||||||
```
|
|
||||||
|
|
||||||
## Importing User Functions
|
|
||||||
|
|
||||||
### DSL Import Statement
|
|
||||||
|
|
||||||
The DSL supports importing Berry modules using the `import` keyword. This is the recommended way to make user functions available in your animations:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Import user functions at the beginning of your DSL file
|
|
||||||
import user_functions
|
|
||||||
|
|
||||||
# Now user functions are available directly
|
|
||||||
animation test = solid(color=blue)
|
|
||||||
test.opacity = my_function()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import Behavior
|
|
||||||
|
|
||||||
- **Module Loading**: `import user_functions` transpiles to Berry `import "user_functions"`
|
|
||||||
- **Function Registration**: The imported module should register functions using `animation.register_user_function()`
|
|
||||||
- **Availability**: Once imported, functions are available throughout the DSL file
|
|
||||||
- **No Compile-Time Checking**: The DSL doesn't validate user function existence at compile time
|
|
||||||
|
|
||||||
### Example User Functions Module
|
|
||||||
|
|
||||||
Create a file called `user_functions.be`:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import animation
|
|
||||||
|
|
||||||
# Define your custom functions
|
|
||||||
def rand_demo(engine)
|
|
||||||
import math
|
|
||||||
return math.rand() % 256 # Random value 0-255
|
|
||||||
end
|
|
||||||
|
|
||||||
def breathing_effect(engine, base_value, amplitude)
|
|
||||||
import math
|
|
||||||
var time_factor = (engine.time_ms / 1000) % 4 # 4-second cycle
|
|
||||||
var breath = math.sin(time_factor * math.pi / 2)
|
|
||||||
return int(base_value + breath * amplitude)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register functions for DSL use
|
|
||||||
animation.register_user_function("rand_demo", rand_demo)
|
|
||||||
animation.register_user_function("breathing", breathing_effect)
|
|
||||||
|
|
||||||
print("User functions loaded!")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Imported Functions in DSL
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import user_functions
|
|
||||||
|
|
||||||
# Simple user function call
|
|
||||||
animation random_test = solid(color=red)
|
|
||||||
random_test.opacity = rand_demo()
|
|
||||||
|
|
||||||
# User function with parameters
|
|
||||||
animation breathing_blue = solid(color=blue)
|
|
||||||
breathing_blue.opacity = breathing(128, 64)
|
|
||||||
|
|
||||||
# User functions in mathematical expressions
|
|
||||||
animation complex = solid(color=green)
|
|
||||||
complex.opacity = max(50, min(255, rand_demo() + 100))
|
|
||||||
|
|
||||||
run random_test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multiple Module Imports
|
|
||||||
|
|
||||||
You can import multiple modules in the same DSL file:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import user_functions # Basic user functions
|
|
||||||
import fire_effects # Fire animation functions
|
|
||||||
import color_utilities # Color manipulation functions
|
|
||||||
|
|
||||||
animation base = solid(color=random_color())
|
|
||||||
base.opacity = breathing(200, 50)
|
|
||||||
|
|
||||||
animation flames = solid(color=red)
|
|
||||||
flames.opacity = fire_intensity(180)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Simple Color Effects
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def solid_bright(engine, color, brightness_percent)
|
|
||||||
var anim = animation.solid_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.brightness = int(brightness_percent * 255 / 100)
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("bright", solid_bright)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation bright_red = solid(color=red)
|
|
||||||
bright_red.opacity = bright(80)
|
|
||||||
|
|
||||||
animation dim_blue = solid(color=blue)
|
|
||||||
dim_blue.opacity = bright(30)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fire Effects
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def custom_fire(engine, intensity, speed)
|
|
||||||
var color_provider = animation.rich_palette(engine)
|
|
||||||
color_provider.palette = animation.PALETTE_FIRE
|
|
||||||
color_provider.cycle_period = speed
|
|
||||||
|
|
||||||
var fire_anim = animation.filled(engine)
|
|
||||||
fire_anim.color_provider = color_provider
|
|
||||||
fire_anim.brightness = intensity
|
|
||||||
return fire_anim
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("fire", custom_fire)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation campfire = solid(color=red)
|
|
||||||
campfire.opacity = fire(200, 2000)
|
|
||||||
|
|
||||||
animation torch = solid(color=orange)
|
|
||||||
torch.opacity = fire(255, 500)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Twinkling Effects
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def twinkles(engine, color, count, period)
|
|
||||||
var anim = animation.twinkle_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.count = count
|
|
||||||
anim.period = period
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("twinkles", twinkles)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation stars = solid(color=white)
|
|
||||||
stars.opacity = twinkles(12, 800ms)
|
|
||||||
|
|
||||||
animation fairy_dust = solid(color=0xFFD700)
|
|
||||||
fairy_dust.opacity = twinkles(8, 600ms)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Position-Based Effects
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def pulse_at(engine, color, position, width, speed)
|
|
||||||
var anim = animation.beacon_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.position = position
|
|
||||||
anim.width = width
|
|
||||||
anim.period = speed
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("pulse_at", pulse_at)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation left_pulse = solid(color=green)
|
|
||||||
left_pulse.position = pulse_at(5, 3, 2000)
|
|
||||||
|
|
||||||
animation right_pulse = solid(color=blue)
|
|
||||||
right_pulse.position = pulse_at(25, 3, 2000)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Examples
|
|
||||||
|
|
||||||
### Multi-Layer Effects
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def rainbow_twinkle(engine, base_speed, twinkle_density)
|
|
||||||
# Create base rainbow animation
|
|
||||||
var rainbow_provider = animation.rich_palette(engine)
|
|
||||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
|
||||||
rainbow_provider.cycle_period = base_speed
|
|
||||||
|
|
||||||
var base_anim = animation.filled(engine)
|
|
||||||
base_anim.color_provider = rainbow_provider
|
|
||||||
base_anim.priority = 1
|
|
||||||
|
|
||||||
# Note: This is a simplified example
|
|
||||||
# Real multi-layer effects would require engine support
|
|
||||||
return base_anim
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("rainbow_sparkle", rainbow_sparkle)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dynamic Palettes
|
|
||||||
|
|
||||||
Since DSL palettes only accept hex colors and predefined color names (not custom colors), use user functions for dynamic palettes with custom colors:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def create_custom_palette(engine, base_color, variation_count, intensity)
|
|
||||||
# Create a palette with variations of the base color
|
|
||||||
var palette_bytes = bytes()
|
|
||||||
|
|
||||||
# Extract RGB components from base color
|
|
||||||
var r = (base_color >> 16) & 0xFF
|
|
||||||
var g = (base_color >> 8) & 0xFF
|
|
||||||
var b = base_color & 0xFF
|
|
||||||
|
|
||||||
# Create palette entries with color variations
|
|
||||||
for i : 0..(variation_count-1)
|
|
||||||
var position = int(i * 255 / (variation_count - 1))
|
|
||||||
var factor = intensity * i / (variation_count - 1) / 255
|
|
||||||
|
|
||||||
var new_r = int(r * factor)
|
|
||||||
var new_g = int(g * factor)
|
|
||||||
var new_b = int(b * factor)
|
|
||||||
|
|
||||||
# Add VRGB entry (Value, Red, Green, Blue)
|
|
||||||
palette_bytes.add(position, 1) # Position
|
|
||||||
palette_bytes.add(new_r, 1) # Red
|
|
||||||
palette_bytes.add(new_g, 1) # Green
|
|
||||||
palette_bytes.add(new_b, 1) # Blue
|
|
||||||
end
|
|
||||||
|
|
||||||
return palette_bytes
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("custom_palette", create_custom_palette)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Use dynamic palette in DSL
|
|
||||||
animation gradient_effect = rich_palette(
|
|
||||||
palette=custom_palette(0xFF6B35, 5, 255)
|
|
||||||
cycle_period=4s
|
|
||||||
)
|
|
||||||
|
|
||||||
run gradient_effect
|
|
||||||
```
|
|
||||||
|
|
||||||
### Preset Configurations
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def police_lights(engine, flash_speed)
|
|
||||||
var anim = animation.pulsating_animation(engine)
|
|
||||||
anim.color = 0xFFFF0000 # Red
|
|
||||||
anim.min_brightness = 0
|
|
||||||
anim.max_brightness = 255
|
|
||||||
anim.period = flash_speed
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
|
|
||||||
def warning_strobe(engine)
|
|
||||||
return police_lights(engine, 200) # Fast strobe
|
|
||||||
end
|
|
||||||
|
|
||||||
def gentle_alert(engine)
|
|
||||||
return police_lights(engine, 1000) # Slow pulse
|
|
||||||
end
|
|
||||||
|
|
||||||
animation.register_user_function("police", police_lights)
|
|
||||||
animation.register_user_function("strobe", warning_strobe)
|
|
||||||
animation.register_user_function("alert", gentle_alert)
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
animation emergency = solid(color=red)
|
|
||||||
emergency.opacity = strobe()
|
|
||||||
|
|
||||||
animation notification = solid(color=yellow)
|
|
||||||
notification.opacity = alert()
|
|
||||||
|
|
||||||
animation custom_police = solid(color=blue)
|
|
||||||
custom_police.opacity = police(500)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Function Organization
|
|
||||||
|
|
||||||
### Single File Approach
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# user_animations.be
|
|
||||||
import animation
|
|
||||||
|
|
||||||
def breathing(engine, color, period)
|
|
||||||
# ... implementation
|
|
||||||
end
|
|
||||||
|
|
||||||
def fire_effect(engine, intensity, speed)
|
|
||||||
# ... implementation
|
|
||||||
end
|
|
||||||
|
|
||||||
def twinkle_effect(engine, color, count, period)
|
|
||||||
# ... implementation
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register all functions
|
|
||||||
animation.register_user_function("breathing", breathing)
|
|
||||||
animation.register_user_function("fire", fire_effect)
|
|
||||||
animation.register_user_function("twinkle", twinkle_effect)
|
|
||||||
|
|
||||||
print("Custom animations loaded!")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modular Approach
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# animations/fire.be
|
|
||||||
def fire_effect(engine, intensity, speed)
|
|
||||||
# ... implementation
|
|
||||||
end
|
|
||||||
|
|
||||||
def torch_effect(engine)
|
|
||||||
return fire_effect(engine, 255, 500)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
'fire': fire_effect,
|
|
||||||
'torch': torch_effect
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# main.be
|
|
||||||
import animation
|
|
||||||
|
|
||||||
# Register functions
|
|
||||||
animation.register_user_function("fire", fire_effects['fire'])
|
|
||||||
animation.register_user_function("torch", fire_effects['torch'])
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Function Design
|
|
||||||
|
|
||||||
1. **Use descriptive names**: `breathing_slow` not `bs`
|
|
||||||
2. **Logical parameter order**: color first, then timing, then modifiers
|
|
||||||
3. **Sensible defaults**: Make functions work with minimal parameters
|
|
||||||
4. **Return animations**: Always return a configured animation object
|
|
||||||
|
|
||||||
### Parameter Handling
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def flexible_pulse(engine, color, period, min_brightness, max_brightness)
|
|
||||||
# Provide defaults for optional parameters
|
|
||||||
if min_brightness == nil min_brightness = 50 end
|
|
||||||
if max_brightness == nil max_brightness = 255 end
|
|
||||||
|
|
||||||
var anim = animation.pulsating_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.period = period
|
|
||||||
anim.min_brightness = min_brightness
|
|
||||||
anim.max_brightness = max_brightness
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def safe_comet(engine, color, tail_length, speed)
|
|
||||||
# Validate parameters
|
|
||||||
if tail_length < 1 tail_length = 1 end
|
|
||||||
if tail_length > 20 tail_length = 20 end
|
|
||||||
if speed < 100 speed = 100 end
|
|
||||||
|
|
||||||
var anim = animation.comet_animation(engine)
|
|
||||||
anim.color = color
|
|
||||||
anim.tail_length = tail_length
|
|
||||||
anim.speed = speed
|
|
||||||
return anim
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Creates a pulsing animation with customizable brightness range
|
|
||||||
# Parameters:
|
|
||||||
# color: The color to pulse (hex or named color)
|
|
||||||
# period: How long one pulse cycle takes (in milliseconds)
|
|
||||||
# min_brightness: Minimum brightness (0-255, default: 50)
|
|
||||||
# max_brightness: Maximum brightness (0-255, default: 255)
|
|
||||||
# Returns: Configured pulse animation
|
|
||||||
def breathing_effect(engine, color, period, min_brightness, max_brightness)
|
|
||||||
# ... implementation
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## User Functions in Computed Parameters
|
|
||||||
|
|
||||||
User functions can be used in computed parameter expressions alongside mathematical functions, creating powerful dynamic animations:
|
|
||||||
|
|
||||||
### Simple User Function in Computed Parameter
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Simple user function call in property assignment
|
|
||||||
animation base = solid(color=blue, priority=10)
|
|
||||||
base.opacity = rand_demo() # User function as computed parameter
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Functions with Mathematical Operations
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Get strip length for calculations
|
|
||||||
set strip_len = strip_length()
|
|
||||||
|
|
||||||
# Mix user functions with mathematical functions
|
|
||||||
animation dynamic_solid = solid(
|
|
||||||
color=purple
|
|
||||||
opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds
|
|
||||||
priority=15
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Functions in Complex Expressions
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Use user function in arithmetic expressions
|
|
||||||
animation random_effect = solid(
|
|
||||||
color=cyan
|
|
||||||
opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value
|
|
||||||
priority=12
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### How It Works
|
|
||||||
|
|
||||||
When you use user functions in computed parameters:
|
|
||||||
|
|
||||||
1. **Automatic Detection**: The transpiler automatically detects user functions in expressions
|
|
||||||
2. **Single Closure**: The entire expression is wrapped in a single efficient closure
|
|
||||||
3. **Engine Access**: User functions receive `engine` in the closure context
|
|
||||||
4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic
|
|
||||||
|
|
||||||
**Generated Code Example:**
|
|
||||||
```berry
|
|
||||||
# DSL code
|
|
||||||
animation.opacity = max(100, breathing(red, 2000))
|
|
||||||
```
|
|
||||||
|
|
||||||
**Transpiles to:**
|
|
||||||
```berry
|
|
||||||
animation.opacity = animation.create_closure_value(engine,
|
|
||||||
def (engine, param_name, time_ms)
|
|
||||||
return (animation._math.max(100, animation.get_user_function('breathing')(engine, 0xFFFF0000, 2000)))
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Available User Functions
|
|
||||||
|
|
||||||
The following user functions are available by default:
|
|
||||||
|
|
||||||
| Function | Parameters | Description |
|
|
||||||
|----------|------------|-------------|
|
|
||||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
|
||||||
|
|
||||||
### Best Practices for Computed Parameters
|
|
||||||
|
|
||||||
1. **Keep expressions readable**: Break complex expressions across multiple lines
|
|
||||||
2. **Use meaningful variable names**: `set strip_len = strip_length()` not `set s = strip_length()`
|
|
||||||
3. **Combine wisely**: Mix user functions with math functions for rich effects
|
|
||||||
4. **Test incrementally**: Start simple and build up complex expressions
|
|
||||||
|
|
||||||
## Loading and Using Functions
|
|
||||||
|
|
||||||
### In Tasmota autoexec.be
|
|
||||||
|
|
||||||
```berry
|
|
||||||
import animation
|
|
||||||
|
|
||||||
# Load your custom functions
|
|
||||||
load("user_animations.be")
|
|
||||||
|
|
||||||
# Now they're available in DSL with import
|
|
||||||
var dsl_code =
|
|
||||||
"import user_functions\n"
|
|
||||||
"\n"
|
|
||||||
"animation my_fire = solid(color=red)\n"
|
|
||||||
"my_fire.opacity = fire(200, 1500)\n"
|
|
||||||
"animation my_twinkles = solid(color=white)\n"
|
|
||||||
"my_twinkles.opacity = twinkle(8, 400ms)\n"
|
|
||||||
"\n"
|
|
||||||
"sequence show {\n"
|
|
||||||
" play my_fire for 10s\n"
|
|
||||||
" play my_twinkles for 5s\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"run show"
|
|
||||||
|
|
||||||
animation_dsl.execute(dsl_code)
|
|
||||||
```
|
|
||||||
|
|
||||||
### From Files
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Save DSL with custom functions
|
|
||||||
var my_show =
|
|
||||||
"import user_functions\n"
|
|
||||||
"\n"
|
|
||||||
"animation campfire = solid(color=orange)\n"
|
|
||||||
"campfire.opacity = fire(180, 2000)\n"
|
|
||||||
"animation stars = solid(color=0xFFFFFF)\n"
|
|
||||||
"stars.opacity = twinkle(6, 600ms)\n"
|
|
||||||
"\n"
|
|
||||||
"sequence night_scene {\n"
|
|
||||||
" play campfire for 30s\n"
|
|
||||||
" play stars for 10s\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"run night_scene"
|
|
||||||
|
|
||||||
# Save to file
|
|
||||||
var f = open("night_scene.anim", "w")
|
|
||||||
f.write(my_show)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# Load and run
|
|
||||||
animation_dsl.load_file("night_scene.anim")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Details
|
|
||||||
|
|
||||||
### Function Signature Requirements
|
|
||||||
|
|
||||||
User functions must follow this exact pattern:
|
|
||||||
|
|
||||||
```berry
|
|
||||||
def function_name(engine, param1, param2, ...)
|
|
||||||
# engine is ALWAYS the first parameter
|
|
||||||
# followed by user-provided parameters
|
|
||||||
return animation_object
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### How the DSL Transpiler Works
|
|
||||||
|
|
||||||
When you write DSL like this:
|
|
||||||
```berry
|
|
||||||
animation my_anim = my_function(arg1, arg2)
|
|
||||||
```
|
|
||||||
|
|
||||||
The transpiler generates Berry code like this:
|
|
||||||
```berry
|
|
||||||
var my_anim_ = animation.get_user_function('my_function')(engine, arg1, arg2)
|
|
||||||
```
|
|
||||||
|
|
||||||
The `engine` parameter is automatically inserted as the first argument.
|
|
||||||
|
|
||||||
### Registration API
|
|
||||||
|
|
||||||
```berry
|
|
||||||
# Register a function
|
|
||||||
animation.register_user_function(name, function)
|
|
||||||
|
|
||||||
# Check if a function is registered
|
|
||||||
if animation.is_user_function("my_function")
|
|
||||||
print("Function is registered")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get a registered function
|
|
||||||
var func = animation.get_user_function("my_function")
|
|
||||||
|
|
||||||
# List all registered functions
|
|
||||||
var functions = animation.list_user_functions()
|
|
||||||
for name : functions
|
|
||||||
print("Registered:", name)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Engine Parameter
|
|
||||||
|
|
||||||
The `engine` parameter provides:
|
|
||||||
- Access to the LED strip: `engine.get_strip_length()`
|
|
||||||
- Current time: `engine.time_ms`
|
|
||||||
- Animation management context
|
|
||||||
|
|
||||||
Always use the provided engine when creating animations - don't create your own engine instances.
|
|
||||||
|
|
||||||
### Return Value Requirements
|
|
||||||
|
|
||||||
User functions must return an animation object that:
|
|
||||||
- Extends `animation.animation` or `animation.pattern`
|
|
||||||
- Is properly configured with the engine
|
|
||||||
- Has all required parameters set
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
The framework handles errors gracefully:
|
|
||||||
- Invalid function names are caught at DSL compile time
|
|
||||||
- Runtime errors in user functions are reported with context
|
|
||||||
- Failed function calls don't crash the animation system
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Function Not Found
|
|
||||||
```
|
|
||||||
Error: Unknown function 'my_function'
|
|
||||||
```
|
|
||||||
- Ensure the function is registered with `animation.register_user_function()`
|
|
||||||
- Check that registration happens before DSL compilation
|
|
||||||
- Verify the function name matches exactly (case-sensitive)
|
|
||||||
|
|
||||||
### Wrong Number of Arguments
|
|
||||||
```
|
|
||||||
Error: Function call failed
|
|
||||||
```
|
|
||||||
- Check that your function signature matches the DSL call
|
|
||||||
- Remember that `engine` is automatically added as the first parameter
|
|
||||||
- Verify all required parameters are provided in the DSL
|
|
||||||
|
|
||||||
### Animation Not Working
|
|
||||||
- Ensure your function returns a valid animation object
|
|
||||||
- Check that the animation is properly configured
|
|
||||||
- Verify that the engine parameter is used correctly
|
|
||||||
|
|
||||||
User-defined functions provide a powerful way to extend the Animation DSL with custom effects while maintaining the clean, declarative syntax that makes the DSL easy to use.
|
|
||||||
Loading…
Reference in New Issue
Block a user