Skip to content

playlist.kt

src/jvmMain/kotlin/afield/runtime/execution/playlist.kt · 139 lines

package afield.runtime.execution

import afield.app.protocol.Execute
import afield.data.table.div
import afield.data.table.get
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.with
import afield.ui.output.md

import afield.audio.synth.*
import afield.document.graph.Box
import afield.document.graph.box
import afield.document.graph.isTrue
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.lifecycle.Frames
import afield.runtime.lifecycle.frames
import afield.runtime.lifecycle.inMain
import afield.app.diagnostics.logging.lprintln
import afield.runtime.execution.begin

Playlist functionality for Field4

This module provides helpers for executing boxes in a defined order based on their playlist property. Boxes with a playlist property (a Double) will be executed in order from lowest to highest value. Between each begin() call, executePlaylist waits pauseList frame() calls (default 1).

User-facing dev doc: dev_docs/Playlist.md (a pointer stub); this KDoc is the source of truth.

var Box.playlist by afield.document.properties.Delegation<Double>()

Extension property to get/set the playlist order for a box

var Box.pauseList by afield.document.properties.Delegation<Double>()

The number of frame() calls executePlaylist makes after begin() before advancing to the next box. Defaults to 1 when unset; coerced to a minimum of 1.

fun Box.findBoxesWithPlaylist(): List<Box>

Finds all boxes (recursively) that have the playlist property set @return List of boxes with playlist property set

fun Box.findBoxesWithPlaylist(): List<Box> {
    val result = mutableListOf<Box>()
    val visited = mutableSetOf<Box>()

    fun visitBox(box: Box) {
        if (box in visited) return
        visited.add(box)

        // Check if this box has playlist property using getPropertyRaw to avoid null pointer
        val playlistValue = box.getPropertyRaw("playlist")
        if (playlistValue is Number) {
            result.add(box)
        }

        // Recursively visit children
        box.children.forEach { child ->
            if (!child.getPropertyRaw("_tombstone").isTrue()) {
                visitBox(child)
            }
        }
    }

    visitBox(this)
    return result
}

fun Box.executePlaylist(strand: String = "default"): Frames

Executes all boxes with the playlist property in order (sorted by playlist value, low to high), calling begin(strand) on each.

Between boxes it waits pauseList frame() calls (default 1, minimum 1) — no fixed wall-clock delay and no floor.

@param strand The strand name to use for execution (default "default") @return Frames object that can be used to control the execution

fun Box.executePlaylist(strand: String = "default"): Frames {
    return frames {
            val boxes = findBoxesWithPlaylist()

            // Sort by playlist value (low to high)
            // We know these boxes have the property set, so we can use the extension property
            val sorted = boxes.sortedBy { it.playlist }

            // Execute each box in order
            for (box in sorted) {

                lprintln(
                    "playlist,execution",
                    "\n\n\n\n -- `${box.name}` begins (playlist) ------------------------------------- "
                )
                try {
                    // Call begin() on the box
                    inMain {
                        box.begin(strand)
                    }

                    // Get the number of frames to wait (pauseList or default 1)
                    // Use getPropertyRaw to safely check if pauseList is set
                    val pauseValue = box.getPropertyRaw("pauseList") as? Number
                    val framesToWait = (pauseValue?.toInt() ?: 1).coerceAtLeast(1)

                    // Wait for the specified number of frames
                    lprintln("playlist,execution", "-- `${box.name}` waiting $framesToWait frame(s)")
                    repeat(framesToWait) {
                        frame()
                    }
                    lprintln("playlist,execution", "-- `${box.name}` finished waiting")
                } catch (e: Exception) {
                    lprintln("playlist,execution", "-- `${box.name}` (playlist) threw:")
                    e.printStackTrace()
                }
            }
        }
    }

fun Box.runPlaylist(strand: String = "default"): Frames

Helper function to execute playlist from root This can be called from any box and will execute the playlist starting from root

@param strand The strand name to use for execution (default "default") @return Frames object that can be used to control the execution

fun Box.runPlaylist(strand: String = "default"): Frames {
    return root.executePlaylist(strand)
}