ludic.ui: an override's path is compared normalised, and an absolute path matches the relative one it ends with - a lab build registers its components as lab/../src/ui/..., so the studio's src/ui/... (or absolute) path matched nothing and the override changed nothing; nothing is normalised while no override stands; examples/library/ui_override_up registers under a .. path as the lab does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 23:00:12 +03:00
parent b3076597e5
commit 82c652ca31
6 changed files with 93 additions and 18 deletions

View file

@ -111,4 +111,5 @@ import "dump.ludic"
import "inspect.ludic"
import "held.ludic"
import "override.ludic"
import "override_paths.ludic"
import "mounted.ludic"

View file

@ -2,11 +2,14 @@
# studio editing a running game's interface (devlink's ui_override). Every read of that path answers
# the given text, a component whose file it is swaps its own and is shown again with its state kept,
# and letting go puts the file back. A game overrides nothing; a read asks an empty list first.
# Paths are compared normalised (a lab build names its components lab/../src/ui/...), and an absolute
# path matches the relative one it ends with.
# from now on `path` reads as `text`; its component (or every importer) is reloaded, state kept
@alloc_ok("a studio's edit: the text kept until it is let go, never a frame")
export function ui_override(ui_st: mut UiState, path: string, text: string) -> void {
export function ui_override(ui_st: mut UiState, path0: string, text: string) -> void {
let t = keep(text)
let path = intern(Path.normalize(path0))
var found = false
for i in 0 .. len(ui_st.ov_path) {
if ui_st.ov_path[i] == path {
@ -15,23 +18,25 @@ export function ui_override(ui_st: mut UiState, path: string, text: string) -> v
}
}
if not found {
push(ui_st.ov_path, intern(path))
push(ui_st.ov_path, path)
push(ui_st.ov_text, t)
}
ov_classes(ui_st, path, t)
ui_file_forget(ui_st, path) # a file an import read is read again; every class re-shown
ov_forget(ui_st, path) # a file an import read is read again; every class re-shown
}
# let go of `path`'s override ("" every override): the file reads from disk again, a component's
# own text comes back
@alloc_ok("a studio's edit, never a frame")
export function ui_override_clear(ui_st: mut UiState, path: string) -> void {
export function ui_override_clear(ui_st: mut UiState, path0: string) -> void {
var path = ""
if path0 != "" { path = Path.normalize(path0) }
var keep_p = new []string
var keep_t = new []string
for i in 0 .. len(ui_st.ov_path) {
let p = ui_st.ov_path[i]
if path == "" or p == path {
if path == "" or ov_same(p, path) {
ov_restore(ui_st, p)
ui_file_forget(ui_st, p)
ov_forget(ui_st, p)
} else {
push(keep_p, p)
push(keep_t, ui_st.ov_text[i])
@ -44,35 +49,35 @@ export function ui_override_clear(ui_st: mut UiState, path: string) -> void {
push(ui_st.ov_text, keep_t[i])
}
}
# is `path` overridden, and its text (null when it is not)
# is `path` overridden
export function ui_overridden(ui_st: UiState, path: string) -> bool { return ov_find(ui_st, path) >= 0 }
function ov_find(ui_st: UiState, path: string) -> int {
for i in 0 .. len(ui_st.ov_path) {
if ui_st.ov_path[i] == path { return i }
}
return -1
}
# a component whose template or stylesheet is `path` takes `text`, its own kept to come back to
@alloc_ok("a studio's edit, never a frame")
function ov_classes(ui_st: mut UiState, path: string, text: string) -> void {
if ui_st.uc_list == null { return }
for i in 0 .. len(ui_st.uc_list) {
let k = ui_st.uc_list[i]
if k.xml_path == path or k.lss_path == path {
let on_xml = k.xml_path != "" and ov_same(path, Path.normalize(k.xml_path))
let on_lss = k.lss_path != "" and ov_same(path, Path.normalize(k.lss_path))
if on_xml or on_lss {
if k.ov_xml == null {
k.ov_xml = k.xml
k.ov_lss = k.lss
}
if k.xml_path == path { k.xml = text } else { k.lss = text }
if on_xml { k.xml = text } else { k.lss = text }
k.root = null
}
}
}
@alloc_ok("a studio's edit, never a frame")
function ov_restore(ui_st: mut UiState, path: string) -> void {
if ui_st.uc_list == null { return }
for i in 0 .. len(ui_st.uc_list) {
let k = ui_st.uc_list[i]
if k.ov_xml != null and (k.xml_path == path or k.lss_path == path) {
if k.xml_path == path { k.xml = k.ov_xml } else { k.lss = k.ov_lss }
let on_xml = k.xml_path != "" and ov_same(path, Path.normalize(k.xml_path))
let on_lss = k.lss_path != "" and ov_same(path, Path.normalize(k.lss_path))
if k.ov_xml != null and (on_xml or on_lss) {
if on_xml { k.xml = k.ov_xml } else { k.lss = k.ov_lss }
if not ov_class_held(ui_st, k, path) {
k.ov_xml = null
k.ov_lss = null
@ -82,10 +87,11 @@ function ov_restore(ui_st: mut UiState, path: string) -> void {
}
}
# does the class's other file still have an override (so its own texts are still needed later)
@alloc_ok("a studio's edit, never a frame")
function ov_class_held(ui_st: UiState, k: UiClass, going: string) -> bool {
for i in 0 .. len(ui_st.ov_path) {
let p = ui_st.ov_path[i]
if p != going and (p == k.xml_path or p == k.lss_path) { return true }
if p != going and (ov_same(p, Path.normalize(k.xml_path)) or ov_same(p, Path.normalize(k.lss_path))) { return true }
}
return false
}

View file

@ -0,0 +1,37 @@
# override_paths.ludic - which override a path is: compared normalised (a lab build names its components
# lab/../src/ui/..., a studio names them src/ui/... or by an absolute path), an absolute path matching
# the relative one it ends with. Nothing is normalised while no override stands.
@alloc_ok("only while a studio's override stands: the path asked, normalised")
function ov_find(ui_st: UiState, path: string) -> int {
if len(ui_st.ov_path) == 0 or path == "" { return -1 } # a game: nothing overridden, nothing made
let p = Path.normalize(path)
for i in 0 .. len(ui_st.ov_path) {
if ov_same(ui_st.ov_path[i], p) { return i }
}
return -1
}
# two normalised paths name one file: equal, or an absolute one ends with /<the relative one>
function ov_same(a: string, b: string) -> bool {
if a == b { return true }
if len(a) > 0 and a[0] == 47 and len(b) > 0 and b[0] != 47 { return ov_ends(a, b) }
if len(b) > 0 and b[0] == 47 and len(a) > 0 and a[0] != 47 { return ov_ends(b, a) }
return false
}
function ov_ends(abs: string, rel: string) -> bool {
let n = len(rel)
let m = len(abs)
if n + 1 > m or abs[m - n - 1] != 47 { return false }
for i in 0 .. n {
if abs[m - n + i] != rel[i] { return false }
}
return true
}
# every loaded file an override names read again, by the path it was loaded under
@alloc_ok("a studio's edit, never a frame")
function ov_forget(ui_st: mut UiState, key: string) -> void {
for i in 0 .. len(ui_st.lib_files) {
let f = ui_st.lib_files[i]
if ov_same(key, Path.normalize(f.path)) { ui_file_forget(ui_st, f.path) }
}
uc_invalidate(ui_st)
}