///|
pub struct SceneThreadBudget {
max_visible_links : Int
max_unread_total : Int
max_orphan_links : Int
max_links_without_open_message : Int
} derive(Debug, Eq)
///|
pub struct SceneThreadPlan {
scene_id : String
total_link_count : Int
visible_link_count : Int
hidden_link_count : Int
unread_total : Int
links_with_unread_count : Int
orphan_link_count : Int
links_without_open_message_count : Int
diagnostics : Array[String]
summary : String
} derive(Debug, Eq)
///|
pub fn scene_thread_budget(
max_visible_links? : Int = 80,
max_unread_total? : Int = 999,
max_orphan_links? : Int = 0,
max_links_without_open_message? : Int = 0,
) -> SceneThreadBudget {
{
max_visible_links,
max_unread_total,
max_orphan_links,
max_links_without_open_message,
}
}
///|
pub fn default_scene_thread_budget() -> SceneThreadBudget {
scene_thread_budget()
}
///|
pub fn plan_scene_threads(
input : Scene,
links : Array[SceneThreadLink],
) -> SceneThreadPlan {
plan_scene_threads_with_budget(input, links, default_scene_thread_budget())
}
///|
pub fn plan_scene_threads_with_budget(
input : Scene,
links : Array[SceneThreadLink],
budget : SceneThreadBudget,
) -> SceneThreadPlan {
plan_scene_threads_with_visible_pool_count(
input,
links,
budget,
links.length(),
)
}
///|
pub fn plan_scene_threads_viewport(
input : Scene,
viewport : SceneViewport,
links : Array[SceneThreadLink],
) -> SceneThreadPlan {
plan_scene_threads_viewport_with_budget(
input,
viewport,
links,
default_scene_thread_budget(),
)
}
///|
pub fn plan_scene_threads_viewport_with_budget(
input : Scene,
viewport : SceneViewport,
links : Array[SceneThreadLink],
budget : SceneThreadBudget,
) -> SceneThreadPlan {
plan_scene_threads_with_visible_pool_count(
input,
links,
budget,
scene_thread_links_in_viewport_count(input, viewport, links),
)
}
///|
fn plan_scene_threads_with_visible_pool_count(
input : Scene,
links : Array[SceneThreadLink],
budget : SceneThreadBudget,
visible_pool_count : Int,
) -> SceneThreadPlan {
let visible_link_count = clamp_visible_count(
visible_pool_count,
budget.max_visible_links,
)
let unread_total = scene_thread_unread_total(links)
let links_with_unread_count = scene_thread_links_with_unread_count(links)
let orphan_link_count = scene_thread_orphan_link_count(input, links)
let links_without_open_message_count = scene_thread_links_without_open_message_count(
links,
)
let diagnostics = scene_thread_diagnostics(
link_count=visible_pool_count,
visible_link_count~,
unread_total~,
orphan_link_count~,
links_without_open_message_count~,
budget~,
)
{
scene_id: input.id,
total_link_count: links.length(),
visible_link_count,
hidden_link_count: links.length() - visible_link_count,
unread_total,
links_with_unread_count,
orphan_link_count,
links_without_open_message_count,
diagnostics,
summary: "Bunnia scene thread plan: scene=\{input.id} links=\{visible_link_count}/\{links.length()} hidden=\{links.length() - visible_link_count} unread_total=\{unread_total} links_with_unread=\{links_with_unread_count} orphan_links=\{orphan_link_count} links_without_open_message=\{links_without_open_message_count} diagnostics=\{diagnostics.length()}",
}
}
///|
fn scene_thread_diagnostics(
link_count~ : Int,
visible_link_count~ : Int,
unread_total~ : Int,
orphan_link_count~ : Int,
links_without_open_message_count~ : Int,
budget~ : SceneThreadBudget,
) -> Array[String] {
let diagnostics : Array[String] = []
if visible_link_count < link_count {
diagnostics.push(
"scene-thread-links-over-budget:\{link_count}/\{budget.max_visible_links}",
)
}
if unread_total > budget.max_unread_total {
diagnostics.push(
"scene-thread-unread-over-budget:\{unread_total}/\{budget.max_unread_total}",
)
}
if orphan_link_count > budget.max_orphan_links {
diagnostics.push(
"scene-thread-orphan-links:\{orphan_link_count}/\{budget.max_orphan_links}",
)
}
if links_without_open_message_count > budget.max_links_without_open_message {
diagnostics.push(
"scene-thread-links-without-open-message:\{links_without_open_message_count}/\{budget.max_links_without_open_message}",
)
}
diagnostics
}
///|
fn clamp_visible_count(total : Int, max_visible_count : Int) -> Int {
if max_visible_count < 0 {
0
} else if max_visible_count < total {
max_visible_count
} else {
total
}
}
///|
fn scene_thread_unread_total(links : Array[SceneThreadLink]) -> Int {
let mut total = 0
for link in links {
total += link.unread_count
}
total
}
///|
fn scene_thread_links_with_unread_count(links : Array[SceneThreadLink]) -> Int {
let mut count = 0
for link in links {
if link.unread_count > 0 {
count += 1
}
}
count
}
///|
fn scene_thread_links_without_open_message_count(
links : Array[SceneThreadLink],
) -> Int {
let mut count = 0
for link in links {
if link.open_message == "" {
count += 1
}
}
count
}
///|
fn scene_thread_orphan_link_count(
input : Scene,
links : Array[SceneThreadLink],
) -> Int {
let mut count = 0
for link in links {
if link.scene_id != input.id ||
!scene_has_subject(input, link.subject_kind, link.subject_id) {
count += 1
}
}
count
}
///|
fn scene_thread_links_in_viewport_count(
input : Scene,
viewport : SceneViewport,
links : Array[SceneThreadLink],
) -> Int {
let mut count = 0
for link in links {
if scene_thread_plan_anchor_in_viewport(
scene_thread_plan_anchor(input, link),
viewport,
) {
count += 1
}
}
count
}
///|
fn scene_thread_plan_anchor(
input : Scene,
link : SceneThreadLink,
) -> (Int, Int)? {
if link.scene_id != input.id {
None
} else {
match link.subject_kind {
"marker" => scene_thread_plan_marker_anchor(input, link.subject_id)
"region" => scene_thread_plan_region_anchor(input, link.subject_id)
_ => None
}
}
}
///|
fn scene_thread_plan_anchor_in_viewport(
anchor : (Int, Int)?,
viewport : SceneViewport,
) -> Bool {
match anchor {
Some((x, y)) =>
x >= viewport.x &&
y >= viewport.y &&
x < viewport.x + viewport.width &&
y < viewport.y + viewport.height
None => false
}
}
///|
fn scene_thread_plan_marker_anchor(
input : Scene,
marker_id : String,
) -> (Int, Int)? {
for scene_layer in input.layers {
for scene_marker in scene_layer.markers {
if scene_marker.id == marker_id {
return Some(
(
scene_marker.x + scene_marker.hit_width / 2,
scene_marker.y + scene_marker.hit_height / 2,
),
)
}
}
}
None
}
///|
fn scene_thread_plan_region_anchor(
input : Scene,
region_id : String,
) -> (Int, Int)? {
for scene_layer in input.layers {
for scene_region in scene_layer.regions {
if scene_region.id == region_id {
return Some(
(
scene_region.x + scene_region.width / 2,
scene_region.y + scene_region.height / 2,
),
)
}
}
}
None
}
///|
fn scene_has_subject(
input : Scene,
subject_kind : String,
subject_id : String,
) -> Bool {
match subject_kind {
"marker" => scene_has_marker(input, subject_id)
"region" => scene_has_region(input, subject_id)
_ => false
}
}
///|
fn scene_has_marker(input : Scene, marker_id : String) -> Bool {
for scene_layer in input.layers {
for scene_marker in scene_layer.markers {
if scene_marker.id == marker_id {
return true
}
}
}
false
}
///|
fn scene_has_region(input : Scene, region_id : String) -> Bool {
for scene_layer in input.layers {
for scene_region in scene_layer.regions {
if scene_region.id == region_id {
return true
}
}
}
false
}