///|
pub struct RenderBudget {
max_nodes : Int
max_depth : Int
max_events : Int
max_initial_data_bytes : Int
max_unwindowed_list_children : Int
max_scene_markers : Int
max_scene_regions : Int
max_scene_assets : Int
max_degraded_scenes : Int
} derive(Debug, Eq)
///|
pub fn default_render_budget() -> RenderBudget {
{
max_nodes: 800,
max_depth: 18,
max_events: 160,
max_initial_data_bytes: 65536,
max_unwindowed_list_children: 64,
max_scene_markers: 600,
max_scene_regions: 300,
max_scene_assets: 64,
max_degraded_scenes: 0,
}
}
///|
pub fn render_budget(
max_nodes~ : Int,
max_depth~ : Int,
max_events~ : Int,
max_initial_data_bytes~ : Int,
max_unwindowed_list_children? : Int = 64,
max_scene_markers? : Int = 600,
max_scene_regions? : Int = 300,
max_scene_assets? : Int = 64,
max_degraded_scenes? : Int = 0,
) -> RenderBudget {
{
max_nodes,
max_depth,
max_events,
max_initial_data_bytes,
max_unwindowed_list_children,
max_scene_markers,
max_scene_regions,
max_scene_assets,
max_degraded_scenes,
}
}
///|
pub struct RenderPlan {
platform : Platform
adapter : PlatformAdapter
root_kind : String
node_count : Int
depth : Int
windowed_list_count : Int
list_total_count : Int
list_visible_count : Int
unwindowed_list_child_count : Int
unkeyed_list_child_count : Int
duplicate_list_key_count : Int
canvas_count : Int
unsupported_canvas_count : Int
scene_count : Int
scene_marker_count : Int
scene_visible_marker_count : Int
scene_region_count : Int
scene_visible_region_count : Int
scene_asset_count : Int
degraded_scene_count : Int
component_kinds : Array[String]
event_messages : Array[String]
diagnostics : Array[String]
summary : String
} derive(Debug, Eq)
///|
pub impl Show for RenderPlan with fn output(self : RenderPlan, logger : &Logger) -> Unit {
logger.write_string(self.summary)
}
///|
pub fn plan(root : Node, platform : Platform) -> RenderPlan {
plan_with_budget(root, platform, default_render_budget())
}
///|
pub fn plan_for_adapter(root : Node, adapter : PlatformAdapter) -> RenderPlan {
plan_for_adapter_with_budget(root, adapter, default_render_budget())
}
///|
pub fn plan_with_budget(
root : Node,
platform : Platform,
budget : RenderBudget,
) -> RenderPlan {
plan_for_adapter_with_budget(root, adapter(platform), budget)
}
///|
pub fn plan_for_adapter_with_budget(
root : Node,
adapter : PlatformAdapter,
budget : RenderBudget,
) -> RenderPlan {
let components : Array[String] = []
let messages : Array[String] = []
collect_node(root, components, messages)
let count = node_count(root)
let depth = max_depth(root)
let windowed_lists = windowed_list_count(root)
let total_items = list_total_count(root)
let visible_items = list_visible_count(root)
let unwindowed_list_children = unwindowed_list_child_count(root)
let unkeyed_list_children = unkeyed_list_child_count(root)
let duplicate_list_keys = duplicate_list_key_count(root)
let canvas_count = component_kind_count(root, "canvas")
let scenes = scene_count(root)
let scene_markers = scene_marker_count(root)
let scene_visible_markers = scene_visible_marker_count(root)
let scene_regions = scene_region_count(root)
let scene_visible_regions = scene_visible_region_count(root)
let scene_assets = scene_asset_count(root)
let degraded_scenes = degraded_scene_count(root)
let unsupported_canvas_count = if canvas_count > 0 && !adapter.supports_canvas {
canvas_count
} else {
0
}
let diagnostics : Array[String] = []
if count > budget.max_nodes {
diagnostics.push("node-count-over-budget:\{count}/\{budget.max_nodes}")
}
if depth > budget.max_depth {
diagnostics.push("depth-over-budget:\{depth}/\{budget.max_depth}")
}
if messages.length() > budget.max_events {
diagnostics.push(
"event-count-over-budget:\{messages.length()}/\{budget.max_events}",
)
}
if unwindowed_list_children > budget.max_unwindowed_list_children {
diagnostics.push(
"unwindowed-list-children-over-budget:\{unwindowed_list_children}/\{budget.max_unwindowed_list_children}",
)
}
if unkeyed_list_children > 0 {
diagnostics.push("list-child-missing-key:\{unkeyed_list_children}")
}
if duplicate_list_keys > 0 {
diagnostics.push("list-child-duplicate-key:\{duplicate_list_keys}")
}
if unsupported_canvas_count > 0 {
diagnostics.push(
"canvas-unsupported:\{adapter.id}:\{unsupported_canvas_count}",
)
}
if scene_visible_markers > budget.max_scene_markers {
diagnostics.push(
"scene-marker-count-over-budget:\{scene_visible_markers}/\{budget.max_scene_markers}",
)
}
if scene_visible_regions > budget.max_scene_regions {
diagnostics.push(
"scene-region-count-over-budget:\{scene_visible_regions}/\{budget.max_scene_regions}",
)
}
if scene_assets > budget.max_scene_assets {
diagnostics.push(
"scene-asset-count-over-budget:\{scene_assets}/\{budget.max_scene_assets}",
)
}
if degraded_scenes > budget.max_degraded_scenes {
diagnostics.push(
"scene-degraded-count-over-budget:\{degraded_scenes}/\{budget.max_degraded_scenes}",
)
}
let summary = "Bunnia render plan: \{adapter.id} root=\{root.kind} nodes=\{count} depth=\{depth} components=\{components.length()} events=\{messages.length()} windowed_lists=\{windowed_lists} list_items=\{visible_items}/\{total_items} unwindowed_list_children=\{unwindowed_list_children} unkeyed_list_children=\{unkeyed_list_children} duplicate_list_keys=\{duplicate_list_keys} canvas=\{canvas_count} unsupported_canvas=\{unsupported_canvas_count} scenes=\{scenes} scene_markers=\{scene_visible_markers}/\{scene_markers} scene_regions=\{scene_visible_regions}/\{scene_regions} scene_assets=\{scene_assets} degraded_scenes=\{degraded_scenes} diagnostics=\{diagnostics.length()}"
{
platform: adapter.platform,
adapter,
root_kind: root.kind,
node_count: count,
depth,
windowed_list_count: windowed_lists,
list_total_count: total_items,
list_visible_count: visible_items,
unwindowed_list_child_count: unwindowed_list_children,
unkeyed_list_child_count: unkeyed_list_children,
duplicate_list_key_count: duplicate_list_keys,
canvas_count,
unsupported_canvas_count,
scene_count: scenes,
scene_marker_count: scene_markers,
scene_visible_marker_count: scene_visible_markers,
scene_region_count: scene_regions,
scene_visible_region_count: scene_visible_regions,
scene_asset_count: scene_assets,
degraded_scene_count: degraded_scenes,
component_kinds: components,
event_messages: messages,
diagnostics,
summary,
}
}
///|
fn component_kind_count(root : Node, kind : String) -> Int {
let mut total = if root.kind == kind { 1 } else { 0 }
for child in root.children {
total += component_kind_count(child, kind)
}
total
}
///|
fn duplicate_list_key_count(root : Node) -> Int {
let mut total = if is_repeated_surface(root) {
immediate_duplicate_key_count(root)
} else {
0
}
for child in root.children {
total += duplicate_list_key_count(child)
}
total
}
///|
fn immediate_duplicate_key_count(root : Node) -> Int {
let mut total = 0
for i in 0.. Int {
let mut total = if is_repeated_surface(root) {
immediate_unkeyed_child_count(root)
} else {
0
}
for child in root.children {
total += unkeyed_list_child_count(child)
}
total
}
///|
fn unwindowed_list_child_count(root : Node) -> Int {
let mut total = if is_repeated_surface(root) && !is_windowed_surface(root) {
immediate_repeated_child_count(root)
} else {
0
}
for child in root.children {
total += unwindowed_list_child_count(child)
}
total
}
///|
fn immediate_repeated_child_count(root : Node) -> Int {
let mut total = 0
for child in root.children {
if child.kind != "text" {
total += 1
}
}
total
}
///|
fn immediate_unkeyed_child_count(root : Node) -> Int {
let mut total = 0
for child in root.children {
if child.key == "" && child.kind != "text" {
total += 1
}
}
total
}
///|
fn is_repeated_surface(root : Node) -> Bool {
is_windowed_surface(root) ||
class_contains(root, "bunnia-list") ||
class_contains(root, "bunnia-message-feed") ||
class_contains(root, "bunnia-communication-trace")
}
///|
fn is_windowed_surface(root : Node) -> Bool {
root.kind == "windowed-list" ||
root.list_total_count > 0 ||
root.list_visible_count > 0 ||
attr_equals(root, "data-bunnia-list-mode", "windowed")
}
///|
fn attr_equals(root : Node, name : String, value : String) -> Bool {
for item in root.attrs {
if item.name == name && item.value == value {
return true
}
}
false
}
///|
fn attr_value(root : Node, name : String) -> String {
for item in root.attrs {
if item.name == name {
return item.value
}
}
""
}
///|
fn attr_int(root : Node, name : String) -> Int? {
let value = attr_value(root, name)
if value == "" {
None
} else {
parse_non_negative_int(value)
}
}
///|
fn parse_non_negative_int(value : String) -> Int? {
let mut total = 0
if value.length() == 0 {
return None
}
for i in 0.. 57 {
return None
}
total = total * 10 + (code - 48)
}
Some(total)
}
///|
fn class_contains(root : Node, value : String) -> Bool {
for item in root.attrs {
if item.name == "class" && item.value.contains(value) {
return true
}
}
false
}
///|
fn is_scene_root(root : Node) -> Bool {
attr_value(root, "data-scene-id") != "" &&
attr_value(root, "data-scene-width") != "" &&
attr_value(root, "data-scene-height") != ""
}
///|
fn scene_count(root : Node) -> Int {
let mut total = if is_scene_root(root) { 1 } else { 0 }
for child in root.children {
total += scene_count(child)
}
total
}
///|
fn scene_marker_count(root : Node) -> Int {
if is_scene_root(root) {
match attr_int(root, "data-markers-total") {
Some(value) => value
None => rendered_scene_marker_count(root)
}
} else {
let mut total = 0
for child in root.children {
total += scene_marker_count(child)
}
total
}
}
///|
fn scene_visible_marker_count(root : Node) -> Int {
if is_scene_root(root) {
match attr_int(root, "data-markers-visible") {
Some(value) => value
None => rendered_scene_marker_count(root)
}
} else {
let mut total = 0
for child in root.children {
total += scene_visible_marker_count(child)
}
total
}
}
///|
fn scene_region_count(root : Node) -> Int {
if is_scene_root(root) {
match attr_int(root, "data-regions-total") {
Some(value) => value
None => rendered_scene_region_count(root)
}
} else {
let mut total = 0
for child in root.children {
total += scene_region_count(child)
}
total
}
}
///|
fn scene_visible_region_count(root : Node) -> Int {
if is_scene_root(root) {
match attr_int(root, "data-regions-visible") {
Some(value) => value
None => rendered_scene_region_count(root)
}
} else {
let mut total = 0
for child in root.children {
total += scene_visible_region_count(child)
}
total
}
}
///|
fn rendered_scene_marker_count(root : Node) -> Int {
let mut total = if attr_value(root, "data-marker-id") != "" { 1 } else { 0 }
for child in root.children {
total += rendered_scene_marker_count(child)
}
total
}
///|
fn rendered_scene_region_count(root : Node) -> Int {
let mut total = if attr_value(root, "data-region-id") != "" { 1 } else { 0 }
for child in root.children {
total += rendered_scene_region_count(child)
}
total
}
///|
fn scene_asset_count(root : Node) -> Int {
let mut total = if is_scene_root(root) {
match attr_int(root, "data-scene-assets") {
Some(value) => value
None => 0
}
} else {
0
}
for child in root.children {
total += scene_asset_count(child)
}
total
}
///|
fn degraded_scene_count(root : Node) -> Int {
let mut total = if attr_equals(root, "data-scene-degraded", "true") {
1
} else {
0
}
for child in root.children {
total += degraded_scene_count(child)
}
total
}
///|
fn windowed_list_count(root : Node) -> Int {
let mut total = if root.list_total_count > 0 || root.list_visible_count > 0 {
1
} else {
0
}
for child in root.children {
total += windowed_list_count(child)
}
total
}
///|
fn list_total_count(root : Node) -> Int {
let mut total = root.list_total_count
for child in root.children {
total += list_total_count(child)
}
total
}
///|
fn list_visible_count(root : Node) -> Int {
let mut total = root.list_visible_count
for child in root.children {
total += list_visible_count(child)
}
total
}
///|
fn collect_node(
root : Node,
components : Array[String],
messages : Array[String],
) -> Unit {
push_unique(components, root.kind)
for binding in root.events {
push_unique(messages, binding.message)
}
for child in root.children {
collect_node(child, components, messages)
}
}
///|
fn push_unique(items : Array[String], value : String) -> Unit {
if !items.contains(value) {
items.push(value)
}
}