feat(launcher): Process.*, Http.save_to/received/expected, App.window_hide/show
Process.spawn/poll/kill/free - non-blocking child processes with no shell: posix_spawn on macOS (process.ll), CreateProcessW with MSVC-quoted arguments and no console window on Windows (process_win.ll), linked only when a program uses Process.*. Http.save_to streams a response body into a file (NSURLSession with a run-time delegate class on macOS, the WinHTTP read loop on Windows); Http.received / Http.expected report progress while it is pending. Freeing a pending request cancels it and parks the slot until the worker has finished. App.window_hide / App.window_show take the game's window off the screen and back without closing it; the run goes on while hidden. Docs, examples, tests and a changeset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c1db0d71ed
commit
43d5eb5b88
31 changed files with 89274 additions and 86727 deletions
33
examples/library/http_save.ludic
Normal file
33
examples/library/http_save.ludic
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# http_save.ludic — Http.save_to / received / expected: a download streamed to a file.
|
||||
#
|
||||
# let h = Http.open("GET", "https://example.com/big.nupkg")
|
||||
# Http.save_to(h, "big.nupkg.part") # before send: the body goes to the file
|
||||
# Http.send(h)
|
||||
# handler Update {
|
||||
# if Http.poll(h) < 0 { # pending: a progress bar
|
||||
# draw_bar(Http.received(h), Http.expected(h)) # expected is -1 until known
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Run as: http_save <source file> <destination file>. The source is read through a
|
||||
# file:// URL (macOS reads those with the same streaming transport), so the check needs
|
||||
# no network: it copies the file and prints the status, the byte counts and whether the
|
||||
# body stayed out of memory. `cmp` the two files afterwards.
|
||||
program HttpSave {
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry {
|
||||
let src = Os.arg(1)
|
||||
let dst = Os.arg(2)
|
||||
let h = Http.open("GET", `file://{src}`)
|
||||
Http.save_to(h, dst)
|
||||
let before = Http.received(h)
|
||||
let unknown = Http.expected(h)
|
||||
Http.send(h)
|
||||
var st = Http.poll(h)
|
||||
while st < 0 { st = Http.poll(h) }
|
||||
let n = Http.received(h)
|
||||
print(`{before} {unknown} {st} {bi(n > 0)} {bi(Http.expected(h) == n)} {bi(Http.body_len(h) == n)} {bi(Http.text(h) == null)}`)
|
||||
Http.free(h)
|
||||
}
|
||||
}
|
||||
42
examples/library/process.ludic
Normal file
42
examples/library/process.ludic
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# process.ludic — Process.* : start a child, poll it without waiting, read its exit code.
|
||||
#
|
||||
# let h = Process.spawn("/path/to/game", ["--profile", "Maroon Lake"])
|
||||
# handler Update { # each frame, never blocking
|
||||
# let code = Process.poll(h) # -1 while it runs, then the exit code
|
||||
# if code >= 0 { Process.free(h) }
|
||||
# }
|
||||
#
|
||||
# No shell is involved: every argument arrives in the child exactly as given. This run
|
||||
# uses /bin/sh as the child (so it is a POSIX check) and prints:
|
||||
# 3 1 7 137 -1 255
|
||||
program ProcessDemo {
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry {
|
||||
# a child that takes a second and exits 3: poll answers -1 many times, then 3
|
||||
let a = Process.spawn("/bin/sh", ["-c", "sleep 1; exit 3"])
|
||||
var polls = 0
|
||||
var code = Process.poll(a)
|
||||
while code < 0 { polls += 1; code = Process.poll(a) }
|
||||
Process.free(a)
|
||||
|
||||
# arguments are passed through untouched - spaces, quotes and all
|
||||
let b = Process.spawn("/bin/sh", ["-c", "test \"$1\" = 'a b\"c' && exit 7; exit 1", "sh", "a b\"c"])
|
||||
var cb = Process.poll(b)
|
||||
while cb < 0 { cb = Process.poll(b) }
|
||||
Process.free(b)
|
||||
|
||||
# a long child, killed: 128 + SIGKILL
|
||||
let c = Process.spawn("/bin/sleep", ["30"])
|
||||
let running = Process.poll(c)
|
||||
Process.kill(c)
|
||||
var cc = Process.poll(c)
|
||||
while cc < 0 { cc = Process.poll(c) }
|
||||
Process.free(c)
|
||||
|
||||
# a program that is not there, and a handle that is not one
|
||||
let d = Process.spawn("/definitely/not/here", new []string)
|
||||
print(`{code} {bi(polls > 100)} {cb} {cc} {d} {Process.poll(99)}`)
|
||||
if running != -1 { print("sleep was not running") }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue