Current section
Files
Jump to
Current section
Files
src/tiramisu/internal/object_cache.gleam
/// Internal object cache management
///
/// This module manages all Three.js objects created by the renderer.
/// It maintains several caches:
/// - Main object cache: Maps IDs to Three.js objects (meshes, lights, groups, etc.)
/// - Animation mixers: Maps IDs to Three.js AnimationMixer instances
/// - Animation actions: Maps IDs to active animation actions
/// - Camera viewports: Maps camera IDs to viewport configurations
/// - Particle systems: Maps IDs to particle system data
///
/// All caches use string IDs generated by the id serializer module.
import gleam/dict.{type Dict}
import gleam/dynamic.{type Dynamic}
import gleam/list
import gleam/option.{type Option}
import tiramisu/asset
import tiramisu/internal/id
/// Opaque type wrapping any Three.js object
pub opaque type ThreeObject {
ThreeObject(object: asset.Object3D)
}
/// Opaque type wrapping a Three.js AnimationMixer
pub opaque type AnimationMixer {
AnimationMixer(mixer: Dynamic)
}
/// Opaque type wrapping a Three.js AnimationAction
pub opaque type AnimationAction {
AnimationAction(action: Dynamic)
}
/// Actions can be single or blended (array of 2)
pub type AnimationActions {
SingleAction(AnimationAction)
BlendedActions(from: AnimationAction, to: AnimationAction)
}
/// Particle system state (will be expanded in particle system module)
pub opaque type ParticleSystem {
ParticleSystem(data: Dynamic)
}
/// Viewport configuration [x, y, width, height]
pub type Viewport {
Viewport(x: Float, y: Float, width: Float, height: Float)
}
/// Complete cache state
pub type CacheState {
CacheState(
/// Main cache of Three.js objects by ID
objects: Dict(String, ThreeObject),
/// Animation mixers by node ID
mixers: Dict(String, AnimationMixer),
/// Current animation actions by node ID
actions: Dict(String, AnimationActions),
/// Camera viewport configurations by camera ID
viewports: Dict(String, Viewport),
/// Particle systems by node ID
particles: Dict(String, ParticleSystem),
)
}
// ============================================================================
// INITIALIZATION
// ============================================================================
/// Create an empty cache state
pub fn init() -> CacheState {
CacheState(
objects: dict.new(),
mixers: dict.new(),
actions: dict.new(),
viewports: dict.new(),
particles: dict.new(),
)
}
// ============================================================================
// OBJECT CACHE OPERATIONS
// ============================================================================
/// Add a Three.js object to the cache
pub fn add_object(cache: CacheState, id: id, object: ThreeObject) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, objects: dict.insert(cache.objects, string_id, object))
}
/// Get a Three.js object from the cache
pub fn get_object(cache: CacheState, id: id) -> Option(ThreeObject) {
let string_id = id.to_string(id)
dict.get(cache.objects, string_id) |> option.from_result
}
/// Remove a Three.js object from the cache
pub fn remove_object(cache: CacheState, id: id) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, objects: dict.delete(cache.objects, string_id))
}
/// Get all cached objects as a list of (ID, Object) tuples
pub fn get_all_objects(cache: CacheState) -> List(#(String, ThreeObject)) {
dict.to_list(cache.objects)
}
// ============================================================================
// ANIMATION MIXER OPERATIONS
// ============================================================================
/// Add an animation mixer to the cache
pub fn add_mixer(cache: CacheState, id: id, mixer: AnimationMixer) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, mixers: dict.insert(cache.mixers, string_id, mixer))
}
/// Get an animation mixer from the cache
pub fn get_mixer(cache: CacheState, id: id) -> Option(AnimationMixer) {
let string_id = id.to_string(id)
dict.get(cache.mixers, string_id) |> option.from_result
}
/// Remove an animation mixer from the cache
pub fn remove_mixer(cache: CacheState, id: id) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, mixers: dict.delete(cache.mixers, string_id))
}
/// Get all mixers as a list
pub fn get_all_mixers(cache: CacheState) -> List(#(String, AnimationMixer)) {
dict.to_list(cache.mixers)
}
// ============================================================================
// ANIMATION ACTION OPERATIONS
// ============================================================================
/// Set the current animation actions for a node
pub fn set_actions(
cache: CacheState,
id: id,
actions: AnimationActions,
) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, actions: dict.insert(cache.actions, string_id, actions))
}
/// Get the current animation actions for a node
pub fn get_actions(cache: CacheState, id: id) -> Option(AnimationActions) {
let string_id = id.to_string(id)
dict.get(cache.actions, string_id) |> option.from_result
}
/// Remove animation actions for a node
pub fn remove_actions(cache: CacheState, id: id) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, actions: dict.delete(cache.actions, string_id))
}
// ============================================================================
// VIEWPORT OPERATIONS
// ============================================================================
/// Set viewport configuration for a camera
pub fn set_viewport(cache: CacheState, id: id, viewport: Viewport) -> CacheState {
let string_id = id.to_string(id)
CacheState(
..cache,
viewports: dict.insert(cache.viewports, string_id, viewport),
)
}
/// Get viewport configuration for a camera
pub fn get_viewport(cache: CacheState, id: id) -> Option(Viewport) {
let string_id = id.to_string(id)
dict.get(cache.viewports, string_id) |> option.from_result
}
/// Remove viewport configuration for a camera
pub fn remove_viewport(cache: CacheState, id: id) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, viewports: dict.delete(cache.viewports, string_id))
}
/// Get all cameras with viewports
pub fn get_cameras_with_viewports(
cache: CacheState,
) -> List(#(ThreeObject, Viewport)) {
dict.to_list(cache.viewports)
|> list.filter_map(fn(entry) {
let #(id, viewport) = entry
case dict.get(cache.objects, id) {
Ok(camera_obj) -> Ok(#(camera_obj, viewport))
Error(_) -> Error(Nil)
}
})
}
// ============================================================================
// PARTICLE SYSTEM OPERATIONS
// ============================================================================
/// Add a particle system to the cache
pub fn add_particle_system(
cache: CacheState,
id: id,
system: ParticleSystem,
) -> CacheState {
let string_id = id.to_string(id)
CacheState(
..cache,
particles: dict.insert(cache.particles, string_id, system),
)
}
/// Get a particle system from the cache
pub fn get_particle_system(cache: CacheState, id: id) -> Option(ParticleSystem) {
let string_id = id.to_string(id)
dict.get(cache.particles, string_id) |> option.from_result
}
/// Remove a particle system from the cache
pub fn remove_particle_system(cache: CacheState, id: id) -> CacheState {
let string_id = id.to_string(id)
CacheState(..cache, particles: dict.delete(cache.particles, string_id))
}
/// Get all particle systems
pub fn get_all_particle_systems(
cache: CacheState,
) -> List(#(String, ParticleSystem)) {
dict.to_list(cache.particles)
}
// ============================================================================
// CLEANUP OPERATIONS
// ============================================================================
/// Remove all cached data for a given ID (object, mixer, actions, viewport, particles)
/// This is used when a node is removed from the scene
pub fn remove_all(cache: CacheState, id: id) -> CacheState {
cache
|> remove_object(id)
|> remove_mixer(id)
|> remove_actions(id)
|> remove_viewport(id)
|> remove_particle_system(id)
}
/// Clear all caches
pub fn clear(_cache: CacheState) -> CacheState {
init()
}
// ============================================================================
// UNWRAP FUNCTIONS (For FFI use)
// ============================================================================
/// Unwrap a ThreeObject to access the underlying Dynamic value
@internal
pub fn unwrap_object(object: ThreeObject) -> asset.Object3D {
object.object
}
/// Unwrap an AnimationMixer to access the underlying Dynamic value
@internal
pub fn unwrap_mixer(mixer: AnimationMixer) -> Dynamic {
mixer.mixer
}
/// Unwrap an AnimationAction to access the underlying Dynamic value
@internal
pub fn unwrap_action(action: AnimationAction) -> Dynamic {
action.action
}
/// Unwrap a ParticleSystem to access the underlying Dynamic value
@internal
pub fn unwrap_particle_system(system: ParticleSystem) -> Dynamic {
system.data
}
/// Wrap a Dynamic value as a ThreeObject
@internal
pub fn wrap_object(object: asset.Object3D) -> ThreeObject {
ThreeObject(object: object)
}
/// Wrap a Dynamic value as an AnimationMixer
@internal
pub fn wrap_mixer(mixer: Dynamic) -> AnimationMixer {
AnimationMixer(mixer: mixer)
}
/// Wrap a Dynamic value as an AnimationAction
@internal
pub fn wrap_action(action: Dynamic) -> AnimationAction {
AnimationAction(action: action)
}
/// Wrap a Dynamic value as a ParticleSystem
@internal
pub fn wrap_particle_system(data: Dynamic) -> ParticleSystem {
ParticleSystem(data: data)
}