Berry animation polished documentation and tutorial (#24266)
@ -22,7 +22,7 @@ The DSL **transpiles to standard Berry code**, so you get the best of both world
|
||||
|
||||
Test and create animations without a Tasmota device using the online emulator:
|
||||
|
||||
[{width=353}](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html){target=_blank}
|
||||
[{width=353}](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html){target=_blank}
|
||||
|
||||
**[https://tasmota.github.io/docs/Tasmota-Berry-emulator/](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html){target=_blank}**
|
||||
|
||||
@ -172,10 +172,10 @@ Animation|Description
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[Quick Start Guide](berry_animation_docs/QUICK_START.md)** - Get running in 5 minutes
|
||||
- **[DSL Reference](berry_animation_docs/DSL_REFERENCE.md)** - Complete syntax reference
|
||||
- **[Examples](berry_animation_docs/EXAMPLES.md)** - Comprehensive examples
|
||||
- **[Animation Classes](berry_animation_docs/ANIMATION_CLASS_HIERARCHY.md)** - All animations and parameters
|
||||
- **[Oscillation Patterns](berry_animation_docs/OSCILLATION_PATTERNS.md)** - Dynamic value waveforms
|
||||
- **[User Functions](berry_animation_docs/USER_FUNCTIONS.md)** - Extend with custom Berry functions
|
||||
- **[Troubleshooting](berry_animation_docs/TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
- **[Quick Start Guide](animation_docs/Quick_Start.md)** - Get running in 5 minutes
|
||||
- **[DSL Reference](animation_docs/Dsl_Reference.md)** - Complete syntax reference
|
||||
- **[Examples](animation_docs/Examples.md)** - Comprehensive examples
|
||||
- **[Animation Classes](animation_docs/Animation_Class_Hierarchy.md)** - All animations and parameters
|
||||
- **[Oscillation Patterns](animation_docs/Oscillation_Patterns.md)** - Dynamic value waveforms
|
||||
- **[User Functions](animation_docs/User_Functions.md)** - Extend with custom Berry functions
|
||||
- **[Troubleshooting](animation_docs/Troubleshooting.md)** - Common issues and solutions
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
# @desc Crenel static with variable number of pulses
|
||||
# Crenel with variable number of pulses
|
||||
|
||||
# Move from 0 to 5 and back
|
||||
set nb_pulse = triangle(min_value = 0, max_value = 5, duration = 2s)
|
||||
set strip_len = strip_length()
|
||||
set period = 4 # pulse_size (2) + low_size (2)
|
||||
set max_pulses = (strip_len + period - 1) / period
|
||||
|
||||
set nb_pulse = triangle(min_value = 0, max_value = max_pulses, duration = 2s)
|
||||
|
||||
# Define a simple crenel 2+2 red/blue
|
||||
animation back = crenel_animation(
|
||||
color = red
|
||||
back_color = blue
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 44 KiB |
@ -0,0 +1,695 @@
|
||||
# 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 == "colors" || 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.
|
||||
1366
lib/libesp32/berry_animation/animation_docs/Animation_Tutorial.md
Normal file
1695
lib/libesp32/berry_animation/animation_docs/Dsl_Reference.md
Normal file
798
lib/libesp32/berry_animation/animation_docs/Dsl_Transpilation.md
Normal file
@ -0,0 +1,798 @@
|
||||
# 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(colors=colors, 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_.colors = animation.create_closure_value(engine, def (engine) return self.colors end)
|
||||
col_.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.
|
||||
479
lib/libesp32/berry_animation/animation_docs/Examples.md
Normal file
@ -0,0 +1,479 @@
|
||||
# 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(colors=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(colors=eye_palette, 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(colors=[red, yellow, green, blue], 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
|
||||
colors rainbow = [
|
||||
(0, red), (42, orange), (85, yellow)
|
||||
(128, green), (170, blue), (213, purple), (255, red)
|
||||
]
|
||||
|
||||
# Create cycling rainbow color
|
||||
color rainbow_cycle = color_cycle(
|
||||
colors=rainbow
|
||||
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! 🎨✨
|
||||
@ -0,0 +1,263 @@
|
||||
# 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.
|
||||
257
lib/libesp32/berry_animation/animation_docs/Quick_Start.md
Normal file
@ -0,0 +1,257 @@
|
||||
# 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(
|
||||
colors=PALETTE_RAINBOW
|
||||
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(
|
||||
colors=sunset
|
||||
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(
|
||||
colors=PALETTE_FIRE
|
||||
period=2s
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
run fire
|
||||
```
|
||||
|
||||
## 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(colors=colors, 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! 🎨✨
|
||||
@ -0,0 +1,858 @@
|
||||
# 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(colors=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.
|
||||
1238
lib/libesp32/berry_animation/animation_docs/Troubleshooting.md
Normal file
677
lib/libesp32/berry_animation/animation_docs/User_Functions.md
Normal file
@ -0,0 +1,677 @@
|
||||
# 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.colors = animation.PALETTE_FIRE
|
||||
color_provider.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.colors = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.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 colors in DSL
|
||||
animation gradient_effect = rich_palette(
|
||||
colors=custom_palette(0xFF6B35, 5, 255)
|
||||
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.
|
||||
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
@ -27,7 +27,7 @@ class CrenelPositionAnimation : animation.animation
|
||||
# Parameter definitions with constraints
|
||||
static var PARAMS = animation.enc_params({
|
||||
# 'color' for the comet head (32-bit ARGB value), inherited from animation class
|
||||
"back_color": {"default": 0xFF000000}, # background color, TODO change to transparent
|
||||
"back_color": {"default": 0x00000000}, # background color (transparent by default)
|
||||
"pos": {"default": 0}, # start of the pulse (in pixel)
|
||||
"pulse_size": {"min": 0, "default": 1}, # number of pixels of the pulse
|
||||
"low_size": {"min": 0, "default": 3}, # number of pixel until next pos - full cycle is 2 + 3
|
||||
@ -52,7 +52,7 @@ class CrenelPositionAnimation : animation.animation
|
||||
var period = int(pulse_size + low_size)
|
||||
|
||||
# Fill background if not transparent
|
||||
if back_color != 0xFF000000
|
||||
if back_color != 0x00000000
|
||||
frame.fill_pixels(frame.pixels, back_color)
|
||||
end
|
||||
|
||||
|
||||
@ -17747,27 +17747,26 @@ be_local_closure(twinkle_gentle, /* name */
|
||||
);
|
||||
/*******************************************************************/
|
||||
|
||||
// compact class 'CrenelPositionAnimation' ktab size: 19, total: 24 (saved 40 bytes)
|
||||
static const bvalue be_ktab_class_CrenelPositionAnimation[19] = {
|
||||
// compact class 'CrenelPositionAnimation' ktab size: 18, total: 23 (saved 40 bytes)
|
||||
static const bvalue be_ktab_class_CrenelPositionAnimation[18] = {
|
||||
/* K0 */ be_nested_str_weak(back_color),
|
||||
/* K1 */ be_nested_str_weak(pos),
|
||||
/* K2 */ be_nested_str_weak(pulse_size),
|
||||
/* K3 */ be_nested_str_weak(low_size),
|
||||
/* K4 */ be_nested_str_weak(nb_pulse),
|
||||
/* K5 */ be_nested_str_weak(color),
|
||||
/* K6 */ be_const_int(-16777216),
|
||||
/* K6 */ be_const_int(0),
|
||||
/* K7 */ be_nested_str_weak(fill_pixels),
|
||||
/* K8 */ be_nested_str_weak(pixels),
|
||||
/* K9 */ be_const_int(0),
|
||||
/* K10 */ be_const_int(1),
|
||||
/* K11 */ be_nested_str_weak(set_pixel_color),
|
||||
/* K12 */ be_nested_str_weak(get_param),
|
||||
/* K13 */ be_nested_str_weak(animation),
|
||||
/* K14 */ be_nested_str_weak(is_value_provider),
|
||||
/* K15 */ be_nested_str_weak(0x_X2508x),
|
||||
/* K16 */ be_nested_str_weak(CrenelPositionAnimation_X28color_X3D_X25s_X2C_X20pos_X3D_X25s_X2C_X20pulse_size_X3D_X25s_X2C_X20low_size_X3D_X25s_X2C_X20nb_pulse_X3D_X25s_X2C_X20priority_X3D_X25s_X2C_X20running_X3D_X25s_X29),
|
||||
/* K17 */ be_nested_str_weak(priority),
|
||||
/* K18 */ be_nested_str_weak(is_running),
|
||||
/* K9 */ be_const_int(1),
|
||||
/* K10 */ be_nested_str_weak(set_pixel_color),
|
||||
/* K11 */ be_nested_str_weak(get_param),
|
||||
/* K12 */ be_nested_str_weak(animation),
|
||||
/* K13 */ be_nested_str_weak(is_value_provider),
|
||||
/* K14 */ be_nested_str_weak(0x_X2508x),
|
||||
/* K15 */ be_nested_str_weak(CrenelPositionAnimation_X28color_X3D_X25s_X2C_X20pos_X3D_X25s_X2C_X20pulse_size_X3D_X25s_X2C_X20low_size_X3D_X25s_X2C_X20nb_pulse_X3D_X25s_X2C_X20priority_X3D_X25s_X2C_X20running_X3D_X25s_X29),
|
||||
/* K16 */ be_nested_str_weak(priority),
|
||||
/* K17 */ be_nested_str_weak(is_running),
|
||||
};
|
||||
|
||||
|
||||
@ -17805,36 +17804,36 @@ be_local_closure(class_CrenelPositionAnimation_render, /* name */
|
||||
0x88340308, // 000C GETMBR R13 R1 K8
|
||||
0x5C380800, // 000D MOVE R14 R4
|
||||
0x7C2C0600, // 000E CALL R11 3
|
||||
0x182C1509, // 000F LE R11 R10 K9
|
||||
0x182C1506, // 000F LE R11 R10 K6
|
||||
0x782E0000, // 0010 JMPF R11 #0012
|
||||
0x5828000A, // 0011 LDCONST R10 K10
|
||||
0x1C2C1109, // 0012 EQ R11 R8 K9
|
||||
0x58280009, // 0011 LDCONST R10 K9
|
||||
0x1C2C1106, // 0012 EQ R11 R8 K6
|
||||
0x782E0001, // 0013 JMPF R11 #0016
|
||||
0x502C0200, // 0014 LDBOOL R11 1 0
|
||||
0x80041600, // 0015 RET 1 R11
|
||||
0x142C1109, // 0016 LT R11 R8 K9
|
||||
0x142C1106, // 0016 LT R11 R8 K6
|
||||
0x782E0006, // 0017 JMPF R11 #001F
|
||||
0x002C0A06, // 0018 ADD R11 R5 R6
|
||||
0x042C170A, // 0019 SUB R11 R11 K10
|
||||
0x042C1709, // 0019 SUB R11 R11 K9
|
||||
0x102C160A, // 001A MOD R11 R11 R10
|
||||
0x042C1606, // 001B SUB R11 R11 R6
|
||||
0x002C170A, // 001C ADD R11 R11 K10
|
||||
0x002C1709, // 001C ADD R11 R11 K9
|
||||
0x5C141600, // 001D MOVE R5 R11
|
||||
0x70020007, // 001E JMP #0027
|
||||
0x442C1400, // 001F NEG R11 R10
|
||||
0x142C0A0B, // 0020 LT R11 R5 R11
|
||||
0x782E0004, // 0021 JMPF R11 #0027
|
||||
0x202C1109, // 0022 NE R11 R8 K9
|
||||
0x202C1106, // 0022 NE R11 R8 K6
|
||||
0x782E0002, // 0023 JMPF R11 #0027
|
||||
0x00140A0A, // 0024 ADD R5 R5 R10
|
||||
0x0420110A, // 0025 SUB R8 R8 K10
|
||||
0x04201109, // 0025 SUB R8 R8 K9
|
||||
0x7001FFF7, // 0026 JMP #001F
|
||||
0x142C0A03, // 0027 LT R11 R5 R3
|
||||
0x782E0014, // 0028 JMPF R11 #003E
|
||||
0x202C1109, // 0029 NE R11 R8 K9
|
||||
0x202C1106, // 0029 NE R11 R8 K6
|
||||
0x782E0012, // 002A JMPF R11 #003E
|
||||
0x582C0009, // 002B LDCONST R11 K9
|
||||
0x14300B09, // 002C LT R12 R5 K9
|
||||
0x582C0006, // 002B LDCONST R11 K6
|
||||
0x14300B06, // 002C LT R12 R5 K6
|
||||
0x78320001, // 002D JMPF R12 #0030
|
||||
0x44300A00, // 002E NEG R12 R5
|
||||
0x5C2C1800, // 002F MOVE R11 R12
|
||||
@ -17843,14 +17842,14 @@ be_local_closure(class_CrenelPositionAnimation_render, /* name */
|
||||
0x00300A0B, // 0032 ADD R12 R5 R11
|
||||
0x14301803, // 0033 LT R12 R12 R3
|
||||
0x78320005, // 0034 JMPF R12 #003B
|
||||
0x8C30030B, // 0035 GETMET R12 R1 K11
|
||||
0x8C30030A, // 0035 GETMET R12 R1 K10
|
||||
0x00380A0B, // 0036 ADD R14 R5 R11
|
||||
0x5C3C1200, // 0037 MOVE R15 R9
|
||||
0x7C300600, // 0038 CALL R12 3
|
||||
0x002C170A, // 0039 ADD R11 R11 K10
|
||||
0x002C1709, // 0039 ADD R11 R11 K9
|
||||
0x7001FFF4, // 003A JMP #0030
|
||||
0x00140A0A, // 003B ADD R5 R5 R10
|
||||
0x0420110A, // 003C SUB R8 R8 K10
|
||||
0x04201109, // 003C SUB R8 R8 K9
|
||||
0x7001FFE8, // 003D JMP #0027
|
||||
0x502C0200, // 003E LDBOOL R11 1 0
|
||||
0x80041600, // 003F RET 1 R11
|
||||
@ -17878,11 +17877,11 @@ be_local_closure(class_CrenelPositionAnimation_tostring, /* name */
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[30]) { /* code */
|
||||
0x4C040000, // 0000 LDNIL R1
|
||||
0x8C08010C, // 0001 GETMET R2 R0 K12
|
||||
0x8C08010B, // 0001 GETMET R2 R0 K11
|
||||
0x58100005, // 0002 LDCONST R4 K5
|
||||
0x7C080400, // 0003 CALL R2 2
|
||||
0xB80E1A00, // 0004 GETNGBL R3 K13
|
||||
0x8C0C070E, // 0005 GETMET R3 R3 K14
|
||||
0xB80E1800, // 0004 GETNGBL R3 K12
|
||||
0x8C0C070D, // 0005 GETMET R3 R3 K13
|
||||
0x5C140400, // 0006 MOVE R5 R2
|
||||
0x7C0C0400, // 0007 CALL R3 2
|
||||
0x780E0004, // 0008 JMPF R3 #000E
|
||||
@ -17892,19 +17891,19 @@ be_local_closure(class_CrenelPositionAnimation_tostring, /* name */
|
||||
0x5C040600, // 000C MOVE R1 R3
|
||||
0x70020004, // 000D JMP #0013
|
||||
0x600C0018, // 000E GETGBL R3 G24
|
||||
0x5810000F, // 000F LDCONST R4 K15
|
||||
0x5810000E, // 000F LDCONST R4 K14
|
||||
0x88140105, // 0010 GETMBR R5 R0 K5
|
||||
0x7C0C0400, // 0011 CALL R3 2
|
||||
0x5C040600, // 0012 MOVE R1 R3
|
||||
0x600C0018, // 0013 GETGBL R3 G24
|
||||
0x58100010, // 0014 LDCONST R4 K16
|
||||
0x5810000F, // 0014 LDCONST R4 K15
|
||||
0x5C140200, // 0015 MOVE R5 R1
|
||||
0x88180101, // 0016 GETMBR R6 R0 K1
|
||||
0x881C0102, // 0017 GETMBR R7 R0 K2
|
||||
0x88200103, // 0018 GETMBR R8 R0 K3
|
||||
0x88240104, // 0019 GETMBR R9 R0 K4
|
||||
0x88280111, // 001A GETMBR R10 R0 K17
|
||||
0x882C0112, // 001B GETMBR R11 R0 K18
|
||||
0x88280110, // 001A GETMBR R10 R0 K16
|
||||
0x882C0111, // 001B GETMBR R11 R0 K17
|
||||
0x7C0C1000, // 001C CALL R3 8
|
||||
0x80040600, // 001D RET 1 R3
|
||||
})
|
||||
@ -17929,7 +17928,7 @@ be_local_class(CrenelPositionAnimation,
|
||||
{ be_const_key_weak(low_size, 4), be_const_bytes_instance(0500000003) },
|
||||
{ be_const_key_weak(pos, 1), be_const_bytes_instance(040000) },
|
||||
{ be_const_key_weak(pulse_size, -1), be_const_bytes_instance(0500000001) },
|
||||
{ be_const_key_weak(back_color, -1), be_const_bytes_instance(0402000000FF) },
|
||||
{ be_const_key_weak(back_color, -1), be_const_bytes_instance(040000) },
|
||||
})) ) } )) },
|
||||
{ be_const_key_weak(render, 2), be_const_closure(class_CrenelPositionAnimation_render_closure) },
|
||||
{ be_const_key_weak(tostring, -1), be_const_closure(class_CrenelPositionAnimation_tostring_closure) },
|
||||
|
||||
@ -36,7 +36,7 @@ def run_tests()
|
||||
|
||||
# Set parameters via virtual member assignment
|
||||
crenel.color = 0xFFFF0000
|
||||
crenel.back_color = 0xFF000000
|
||||
crenel.back_color = 0x00000000 # transparent (default)
|
||||
crenel.pos = 4
|
||||
crenel.pulse_size = 2
|
||||
crenel.low_size = 3
|
||||
@ -95,7 +95,7 @@ def run_tests()
|
||||
crenel.pulse_size = 2
|
||||
crenel.low_size = 3
|
||||
crenel.nb_pulse = -1 # Infinite
|
||||
crenel.back_color = 0xFF000000 # Transparent
|
||||
crenel.back_color = 0x00000000 # Transparent (default)
|
||||
crenel.start()
|
||||
|
||||
var rendered = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
@ -124,7 +124,7 @@ def run_tests()
|
||||
|
||||
# Test 7: Limited number of pulses
|
||||
frame.clear()
|
||||
crenel.back_color = 0xFF000000 # Transparent background
|
||||
crenel.back_color = 0x00000000 # Transparent background (default)
|
||||
crenel.nb_pulse = 2 # Only 2 pulses
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ def test_crenel_with_integer_color()
|
||||
|
||||
# Set parameters via virtual member assignment
|
||||
crenel.color = red_color
|
||||
crenel.back_color = 0xFF000000 # transparent
|
||||
crenel.back_color = 0x00000000 # transparent (default)
|
||||
crenel.pos = 0
|
||||
crenel.pulse_size = 3
|
||||
crenel.low_size = 2
|
||||
@ -64,7 +64,7 @@ def test_crenel_with_color_provider()
|
||||
|
||||
# Set parameters via virtual member assignment
|
||||
crenel.color = color_provider # ColorProvider
|
||||
crenel.back_color = 0xFF000000 # transparent
|
||||
crenel.back_color = 0x00000000 # transparent (default)
|
||||
crenel.pos = 1
|
||||
crenel.pulse_size = 2
|
||||
crenel.low_size = 3
|
||||
@ -106,7 +106,7 @@ def test_crenel_with_dynamic_color_provider()
|
||||
|
||||
# Set parameters via virtual member assignment
|
||||
crenel.color = palette_provider # dynamic ColorProvider
|
||||
crenel.back_color = 0xFF000000 # transparent
|
||||
crenel.back_color = 0x00000000 # transparent (default)
|
||||
crenel.pos = 0
|
||||
crenel.pulse_size = 4
|
||||
crenel.low_size = 1
|
||||
@ -154,7 +154,7 @@ def test_crenel_with_generic_value_provider()
|
||||
|
||||
# Set parameters via virtual member assignment
|
||||
crenel.color = static_provider # generic ValueProvider
|
||||
crenel.back_color = 0xFF000000 # transparent
|
||||
crenel.back_color = 0x00000000 # transparent (default)
|
||||
crenel.pos = 2
|
||||
crenel.pulse_size = 3
|
||||
crenel.low_size = 2
|
||||
@ -191,7 +191,7 @@ def test_crenel_set_color_methods()
|
||||
|
||||
# Set initial parameters
|
||||
crenel.color = 0xFFFF0000 # red
|
||||
crenel.back_color = 0xFF000000 # transparent
|
||||
crenel.back_color = 0x00000000 # transparent (default)
|
||||
crenel.pos = 0
|
||||
crenel.pulse_size = 2
|
||||
crenel.low_size = 1
|
||||
@ -233,7 +233,7 @@ def test_crenel_tostring()
|
||||
# Test with integer color
|
||||
var crenel_int = animation.crenel_animation(engine)
|
||||
crenel_int.color = 0xFFFF0000
|
||||
crenel_int.back_color = 0xFF000000
|
||||
crenel_int.back_color = 0x00000000 # transparent (default)
|
||||
crenel_int.pos = 0
|
||||
crenel_int.pulse_size = 2
|
||||
crenel_int.low_size = 1
|
||||
@ -254,7 +254,7 @@ def test_crenel_tostring()
|
||||
|
||||
var crenel_provider = animation.crenel_animation(engine)
|
||||
crenel_provider.color = color_provider
|
||||
crenel_provider.back_color = 0xFF000000
|
||||
crenel_provider.back_color = 0x00000000 # transparent (default)
|
||||
crenel_provider.pos = 0
|
||||
crenel_provider.pulse_size = 2
|
||||
crenel_provider.low_size = 1
|
||||
|
||||