Files
jak-project/goal_src/jak1/engine/load/loader.gc
T
2026-07-28 12:00:35 -07:00

803 lines
41 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "engine/load/loader-h.gc")
(require "engine/anim/joint.gc")
(require "engine/load/load-dgo.gc")
(require "engine/sound/gsound.gc")
(require "engine/game/task/hint-control-h.gc")
(require "engine/common-obs/process-drawable-h.gc")
(require "engine/game/game-info-h.gc")
;; note: changed for high fps
;; DECOMP BEGINS
(defmethod inspect ((this load-dir))
"Print all the stuff in a load-dir"
(format #t "[~8x] ~A~%" this (-> this type))
(format #t "~Tlevel: ~A~%" (-> this lev))
(format #t "~Tallocated-length: ~D~%" (-> this string-array allocated-length))
(format #t "~Tlength: ~D~%" (-> this string-array length))
(dotimes (i (-> this string-array length))
(format #t
"~T [~D] ~S ~A (~D bytes)~%"
i
(-> this string-array i)
(-> this data-array i)
(mem-size (-> this data-array i) #f (mem-usage-flags))))
this)
(defmethod mem-usage ((this load-dir) (usage memory-usage-block) (flags mem-usage-flags))
"Account for the directory and both parallel arrays in the array memory category, then add
the memory owned by every loaded data object. Pass flags through to each child."
(mem-usage-add! usage array 1 (asize-of this))
(mem-usage-add! usage array 0 (asize-of (-> this string-array)))
(mem-usage-add! usage array 0 (asize-of (-> this data-array)))
(dotimes (i (-> this data-array length))
(mem-usage (-> this data-array i) usage flags))
(the-as load-dir #f))
(defmethod load-to-heap-by-name ((this load-dir-art-group) (art-name string) (do-reload symbol) (heap kheap) (version int))
"Return the art group named art-name from this directory. If it is already present, reload
and replace it only when do-reload is true and the new debug load succeeds. Otherwise load,
validate, and append a new entry. Return #f when a new load fails."
;; see if we already have it.
(let ((name-array (-> this string-array)))
(dotimes (i (-> name-array length))
(when (string= art-name (-> name-array i))
(when do-reload
;; we have it, reload it if requested
(let ((reloaded-group (art-group-load-check art-name heap version)))
(if reloaded-group (set! (-> this art-group-array i) reloaded-group))))
(return (-> this art-group-array i))))
;; The name isn't present, so try to add it.
(let ((new-group (art-group-load-check art-name heap version)))
(when new-group
(set! (-> name-array (-> name-array length)) art-name)
(set! (-> this art-group-array (-> name-array length)) new-group)
(+! (-> name-array length) 1)
(+! (-> this art-group-array length) 1))
new-group)))
(defmethod set-loaded-art ((this load-dir-art-group) (group art-group))
"Insert an already loaded group. Replace the group with the same name when present;
otherwise append its name and pointer to the parallel arrays."
(let ((name-array (-> this string-array)))
(dotimes (i (-> name-array length))
(when (string= (-> group name) (-> name-array i))
(set! (-> this art-group-array i) group)
(return (-> this art-group-array i))))
(set! (-> name-array (-> name-array length)) (-> group name))
(set! (-> this art-group-array (-> name-array length)) group)
(+! (-> name-array length) 1))
(set! (-> this art-group-array length) (+ (-> this art-group-array length) 1))
group)
(#unless PC_PORT
(defun drawable-load ((source drawable) (heap kheap))
"Load and log in a drawable when source is a filename, or log in source directly when it is
already a drawable. The EE path moves low-stack loads to the protected kernel stack. Return #f
for an invalid object."
(cond
((type-type? (-> source type) string)
(with-sp (protect sp
(if (< sp *stack-top*) (set! sp (&+ *kernel-sp* -1024)))
(let ((loaded-drawable (the-as drawable (loado (the-as string source) heap))))
(if (and loaded-drawable (type-type? (-> loaded-drawable type) drawable)) (login loaded-drawable))))))
((type-type? (-> source type) drawable) (login source)))))
(#when PC_PORT
(defun drawable-load ((source drawable) (heap kheap))
"Load and log in a drawable when source is a filename, or log in source directly when it is
already a drawable. The EE path moves low-stack loads to the protected kernel stack. Return #f
for an invalid object."
(cond
((type-type? (-> source type) string)
(let ((loaded-drawable (the-as drawable (loado (the-as string source) heap))))
(if (and loaded-drawable (type-type? (-> loaded-drawable type) drawable)) (login loaded-drawable))))
((type-type? (-> source type) drawable) (login source)))))
(#unless PC_PORT
(defun art-load ((name string) (heap kheap))
"Load name into heap, verify that the result is art, and log it in. The EE path moves low-stack
loads to the protected kernel stack; return #f when the loaded object is not art."
(with-sp (protect sp
(if (< sp *stack-top*) (set! sp (&+ *kernel-sp* -1024)))
(let ((loaded-art (the-as art (loado name heap))))
(if (type-type? (-> loaded-art type) art) (login loaded-art) (the-as art #f)))))))
(#when PC_PORT
(defun art-load ((name string) (heap kheap))
"Load name into heap, verify that the result is art, and log it in. The EE path moves low-stack
loads to the protected kernel stack; return #f when the loaded object is not art."
(let ((loaded-art (the-as art (loado name heap))))
(if (type-type? (-> loaded-art type) art) (login loaded-art) (the-as art #f)))))
(#unless PC_PORT
(defun art-group-load-check ((name string) (heap kheap) (version int))
"In a debug build, load the versioned art-group file into heap, validate its type and file
version, and log it in. Return #f when debug memory is unavailable or any validation fails."
(when *debug-segment*
(with-sp (protect sp
(if (< sp *stack-top*) (set! sp (&+ *kernel-sp* -1024)))
(let ((loaded-group (the-as art-group (loado (make-file-name (file-kind art-group) name version #f) heap))))
(cond
((not loaded-group) (format 0 "ERROR: art-group ~A is not a valid file.~%" name) (the-as art-group #f))
((not (type-type? (-> loaded-group type) art-group))
(format 0 "ERROR: art-group ~A is not a art-group.~%" name)
(the-as art-group #f))
((not (file-info-correct-version? (-> loaded-group info) (file-kind art-group) version))
;; file-info-correct-version? prints the detailed error.
(the-as art-group #f))
(else (login loaded-group)))))))))
(#when PC_PORT
(defun art-group-load-check ((name string) (heap kheap) (version int))
"In a debug build, load the versioned art-group file into heap, validate its type and file
version, and log it in. Return #f when debug memory is unavailable or any validation fails."
(when *debug-segment*
(let ((loaded-group (the-as art-group (loado (make-file-name (file-kind art-group) name version #f) heap))))
(cond
((not loaded-group) (format 0 "ERROR: art-group ~A is not a valid file.~%" name) (the-as art-group #f))
((not (type-type? (-> loaded-group type) art-group))
(format 0 "ERROR: art-group ~A is not a art-group.~%" name)
(the-as art-group #f))
((not (file-info-correct-version? (-> loaded-group info) (file-kind art-group) version))
;; file-info-correct-version? prints the detailed error.
(the-as art-group #f))
(else (login loaded-group)))))))
(defmethod set-pending-file ((this external-art-buffer) (name string) (part int) (owner handle) (priority float))
"Set the file part, owning process handle, and priority that this buffer should service on
its next update."
(set! (-> this pending-load-file) name)
(set! (-> this pending-load-file-part) part)
(set! (-> this pending-load-file-owner) owner)
(set! (-> this pending-load-file-priority) priority)
0)
(defmethod unlock! ((this external-art-buffer))
"Clear the inter-buffer activation lock."
(declare (inline))
(set! (-> this locked?) #f)
#f)
(defmethod inactive? ((this external-art-buffer))
"Return true unless this buffer currently has an active, linked art group."
(declare (inline))
(!= (-> this status) 'active))
(defmethod file-status ((this external-art-buffer) (name string) (part int))
"Return this buffer's state for the requested name and part. Return pending when it is queued
but not yet the current load, and #f when it is not requested by this buffer."
(when (and (name= (-> this pending-load-file) name) (= (-> this pending-load-file-part) part))
;; the file is at least wanting to load
(if (and (name= (-> this load-file) name) (= (-> this load-file-part) part))
(-> this status) ;; file is loaded or loading
'pending ;; file has not started loading yet.
)))
(defmethod link-art! ((this art-group))
"Install every joint animation in this group into a resident master art group. Search level
slots 2 through 0, prefer the animation's authored index when it is valid and empty, and
otherwise use an empty slot. Print an error for an animation that cannot be linked."
(when this
(countdown (i (-> this length))
(let* ((art-elt (-> this data i))
(janim (if (and (nonzero? art-elt) (type-type? (-> art-elt type) art-joint-anim)) (the-as art-joint-anim art-elt)))
(success #f))
(when janim
;; a countdown with a label right at the start
(let ((level-index 3))
(while (begin
(label cfg-22)
(nonzero? level-index))
;; loop over levels, looking for the master art group for this joint animation.
(+! level-index -1)
(let ((janim-group (art-group-get-by-name (-> *level* level level-index) (-> janim master-art-group-name))))
(when janim-group
(cond
((and (< (-> janim master-art-group-index) (-> janim-group length)) ;; index is valid
(not (-> janim-group data (-> janim master-art-group-index))) ;; doesn't already have it loaded
)
;; link!
(set! (-> janim-group data (-> janim master-art-group-index)) janim)
(set! success #t))
(else
;; if the specified index is no good, just try looking for somewhere else.
(countdown (slot-index (-> janim-group length))
(when (not (-> janim-group data slot-index))
;; found an empty one!
(set! (-> janim-group data slot-index) janim)
(set! success #t)
(goto cfg-22)))))))))
(if (not success) (format 0 "ERROR: ~A could not find a master slot to link for ~A.~%" (-> this name) janim))))))
this)
(defmethod unlink-art! ((this art-group))
"Remove this group's joint animations from matching resident master art groups in level slots
2 through 0. Print an error for an animation that was not linked anywhere."
(when this
(countdown (i (-> this length))
(let* ((art-elt (-> this data i))
(janim (if (and (nonzero? art-elt) (type-type? (-> art-elt type) art-joint-anim)) (the-as art-joint-anim art-elt)))
(success #f))
(when janim
(let ((level-index 3))
(while (begin
(label cfg-16)
(nonzero? level-index))
(+! level-index -1)
(let ((janim-group (art-group-get-by-name (-> *level* level level-index) (-> janim master-art-group-name))))
(when janim-group
(countdown (slot-index (-> janim-group length))
(when (= janim (-> janim-group data slot-index))
(set! (-> janim-group data slot-index) #f)
(set! success #t)
(goto cfg-16)))))))
(if (not success) (format 0 "ERROR: ~A could not find a master slot to unlink for ~A.~%" (-> this name) janim))))))
0)
(defmethod link-file ((this external-art-buffer) (group art-group))
"Link group's joint animations into their resident master groups and make it this buffer's
active art group."
(when group
(link-art! group)
(set! (-> this art-group) group))
group)
(defmethod unlink-file ((this external-art-buffer) (group art-group))
"Unlink group's joint animations and clear this buffer's active art-group pointer."
(when group
(unlink-art! group)
(set! (-> this art-group) #f))
0)
(defmethod update ((this external-art-buffer))
"Advance this buffer's streaming state. Adopt changed pending requests, cancel displaced
loads, initialize the fixed spool heap, start and poll the aligned STR transfer, link and
validate the resulting art group, serialize activation with the other buffer, and unload data
whose request was cleared. Requests whose owner no longer exists are discarded."
(when (or (not (name= (-> this pending-load-file) (-> this load-file)))
(!= (-> this pending-load-file-part) (-> this load-file-part)))
;; we're loading a different file, or a different part of a file
(unless (handle->process (-> this pending-load-file-owner))
;; nobody owns the loading file so we can discard it
(set! (-> this pending-load-file) #f)
(set! (-> this pending-load-file-part) -1)
(set! (-> this pending-load-file-owner) (the-as handle #f))
(set! (-> this pending-load-file-priority) SPOOL_PRIORITY_LOWEST))
(when (= (-> this status) 'initialize)
;; we need to initialize the heap
(let ((buffer-heap (-> this heap)))
;; Scary: this is a hard coded address that points to the kernel memory.
;; it turns out the kernel doesn't need this. So we can use it!
(set! (-> buffer-heap base) (the-as pointer (+ #x84000 (* SPOOL_HEAP_SIZE (-> this index)))))
(set! (-> buffer-heap current) (-> buffer-heap base))
(set! (-> buffer-heap top-base) (&+ (-> buffer-heap base) SPOOL_HEAP_SIZE))
(set! (-> buffer-heap top) (-> buffer-heap top-base)))
(set! (-> this status) 'inactive)
;; heap is now allocated, but there is no data to use
)
(cond
((-> this load-file)
;; the buffer is working on a file
(if (= (-> this status) 'loading)
;; something else is already loading, cancel that because we want something else
(str-load-cancel))
;; now nothing is loaded!
(set! (-> this load-file) #f)
(set! (-> this load-file-part) -1)
(set! (-> this load-file-owner) (the-as handle #f))
(set! (-> this load-file-priority) SPOOL_PRIORITY_LOWEST)
;; on the next time through, we will set the actual load file.
)
(else
;; we have officially chosen to load this file
(set! (-> this load-file) (-> this pending-load-file))
(set! (-> this load-file-part) (-> this pending-load-file-part))
(set! (-> this load-file-owner) (-> this pending-load-file-owner))
(set! (-> this load-file-priority) (-> this pending-load-file-priority)))))
(label cfg-18)
(cond
((-> this load-file)
;; something is being worked on
(case (-> this status)
(('active 'reserved)
;; file is loaded and usable (or reserved)
)
(('error)
;; oops an error happened. make this buffer unusable now.
(set! (-> this status) 'inactive)
(set! (-> this load-file) #f)
(set! (-> this load-file-part) -1)
(set! (-> this load-file-owner) (the-as handle #f))
(set! (-> this load-file-priority) SPOOL_PRIORITY_LOWEST)
(set! (-> this pending-load-file) #f)
(set! (-> this pending-load-file-part) -1)
(set! (-> this pending-load-file-owner) (the-as handle #f))
(set! (-> this pending-load-file-priority) SPOOL_PRIORITY_LOWEST)
(set! (-> this art-group) #f))
(('inactive)
;; no usable data here, fill the buffer
(kheap-reset (-> this heap))
(cond
((string= (-> this load-file) "reserved") ;; we want to reserve this buffer for something (not loading an str file)
(cond
((-> *art-control* reserve-buffer)
(format 0 "ERROR: trying double reserve ~A when ~A is reserved~%" this (-> *art-control* reserve-buffer)))
(else
(set! (-> this status) 'reserved)
(set! (-> *art-control* reserve-buffer) this) ;; this buffer is reserved
)))
((and (!= (-> *level* loading-level) (-> *level* level-default)) (< (meters 20) (-> this load-file-priority)))
;; unused cond
)
((str-load (-> this load-file) (-> this load-file-part) (the pointer (align64 (-> this heap current))) #x3fc00) ;; try to start load
;; load has started!!
(set! (-> this status) 'loading))))
(('loading)
;; loading...
(case (str-load-status (&-> this len))
(('error)
;; something went wrong. oh well.
(set! (-> this status) 'error))
(('busy)
;; loading. we have nothing to do.
)
(else
;; done!!
;; not sure it is a good idea to assume that this is a success...
(set! (-> this buf) (the pointer (align64 (-> this heap current))))
(set! (-> this status) 'loaded)
(goto cfg-18) ;; go back and check status again!
)))
(('loaded)
;; file is loaded. link it and see if we're good
(let ((file-data (-> this buf)))
(set! (-> this art-group)
(the-as art-group (link (the-as pointer file-data) (-> this load-file data) (-> this len) (-> this heap) 0))))
(let ((loaded-group (-> this art-group))
(file-name (-> this load-file)))
(cond
((not loaded-group)
(format 0 "ERROR: art-group ~A part ~D is not a valid file.~%" file-name (-> this load-file-part))
(set! (-> this status) 'error))
((not (type-type? (-> loaded-group type) art-group))
(format 0 "ERROR: art-group ~A part ~D is not a art-group.~%" file-name (-> this load-file-part))
(set! (-> this status) 'error))
((not (file-info-correct-version? (-> loaded-group info) (file-kind art-group) 0))
;;(format 0 "ERROR: art-group ~A part ~D is the wrong version.~%" file-name (-> this load-file-part))
(set! (-> this status) 'error))
(else
(login loaded-group)
(set! (-> this status) 'locked) ;; make file ready to be used
))))
(('locked)
;; this buffer is locked and needs to be unlocked before it can be used.
;; only one buffer can be active at a time. The other buffer is locked to prevent it from activating.
(when (and (not (-> this locked?)) (handle->process (-> this load-file-owner)))
;; we want to be used, unlock this buffer and lock the other just in case.
(link-file this (-> this art-group))
(set! (-> this other locked?) #t) ;; prevent it from becoming active
(set! (-> this status) 'active)
(goto cfg-18)))))
(else
;; we want to get rid of the file!
(case (-> this status)
(('initialize)
;; this was done earlier
)
(('reserved)
;; this buffer is reserved
(cond
((= (-> *art-control* reserve-buffer) this) ;; yep it's this one!
(set! (-> *art-control* reserve-buffer) #f)
(set! (-> this status) 'inactive))
(else (format 0 "ERROR: trying tro free ~A when ~A is reserved~%" this (-> *art-control* reserve-buffer)))))
(('active)
;; buffer is in use. not anymore!
(unlink-file this (-> this art-group))
(let ((buffer-heap (-> this heap))) (set! (-> buffer-heap current) (-> buffer-heap base)))
(set! (-> this art-group) #f)
(set! (-> this status) 'inactive)
;; if the other is locked due to us, unlock it, then update it so it activates.
(when (-> this other locked?)
(unlock! (-> this other))
(update (-> this other))))
(else
;; some other scenario, just get rid of the whole thing.
(let ((buffer-heap (-> this heap))) (set! (-> buffer-heap current) (-> buffer-heap base)))
(set! (-> this art-group) #f)
(set! (-> this status) 'inactive)))))
0)
;; start loading a spooled anim if we think one is about to be used, e.g. when approaching a fuel cell or npc
;; (some processes may want to wait for the stream to be preloaded, which won't happen with this disabled)
(define *preload-spool-anims* #t)
(defmethod file-status ((this external-art-control) (name string) (part int))
"Return the first buffer state for name and part, or #f when neither buffer is servicing the
request."
(dotimes (i 2)
(awhen (file-status (-> this buffer i) name part)
(return it)))
#f)
(defmethod update ((this external-art-control) (debug-print symbol))
"Assign the two streaming buffers to the highest-priority eligible requests, retain buffers
already serving those requests, advance both buffer state machines, and queue the best
animation stream for audio preloading. Print request and buffer state when debug-print is true
and loader display is enabled."
;; if somebody wants a reserve buffer, they will set this to 1.
(if (nonzero? (-> this reserve-buffer-count))
(spool-push this "reserved" 0 *dproc* (if (-> this reserve-buffer) -110.0 -0.5)))
;; frame-lock will get set to #t if something is assigned to this buffer in this update.
(dotimes (i 2)
(set! (-> this buffer i frame-lock) #f))
;; buffers assigned from this call to update
(dotimes (i 3)
(set! (-> this rec i buf2) #f))
;; update existing buffers from their recs
(dotimes (request-index 2)
(let ((request (-> this rec request-index)))
(when (-> request name)
;; iterate over the two buffers
(dotimes (buffer-index 2)
(when (and (file-status (-> this buffer buffer-index) (-> request name) (-> request parts)) ;; this buffer holds the file for the rec
(not (-> this buffer buffer-index frame-lock))) ;; and nothing has frame-locked this buffer
;; so we frame lock it to prevent it from being kicked out
(set! (-> this buffer buffer-index frame-lock) #t)
;; remember what buffer
(set! (-> request buf2) (-> this buffer buffer-index))
;; update owner and priority.
(set! (-> this buffer buffer-index pending-load-file-owner) (-> request owner))
(set! (-> this buffer buffer-index load-file-owner) (-> request owner))
(set! (-> this buffer buffer-index pending-load-file-priority) (-> request priority))
(set! (-> this buffer buffer-index load-file-priority) (-> request priority))
(goto cfg-24)))))
(label cfg-24))
;; preload recs
;; iterate over recs
(dotimes (request-index 2)
(let ((request (-> this rec request-index)))
;; rec wants to load something, but doesn't have a buffer already
(when (and (-> request name) (not (-> request buf2)))
;; skip if we aren't preloading, or have a positive priority.
(if (and (not *preload-spool-anims*) (>= (-> request priority) 0.0))
;; not in use, move on
(goto cfg-46))
;; search for a buffer for preloading
(dotimes (buffer-index 2)
;; can't steal one that's already assigned
(when (not (-> this buffer buffer-index frame-lock))
;; do the assignment!
(set! (-> this buffer buffer-index frame-lock) #t)
(set-pending-file (-> this buffer buffer-index)
(-> request name)
(-> request parts)
(-> request owner)
(-> request priority))
(set! (-> request buf2) (-> this buffer buffer-index))
(goto cfg-46)))))
(label cfg-46))
;; A locked top request cannot activate while the other buffer remains in use. Unless either
;; buffer is reserved, clear the other request so its update unloads it and releases the lock.
(when (not (-> this reserve-buffer))
(let ((highest-buffer (-> this rec 0 buf2))) ;; top priority buffer
(if (and highest-buffer
(-> highest-buffer locked?)
(not (string= (-> highest-buffer pending-load-file) "reserved"))
(not (string= (-> highest-buffer other pending-load-file) "reserved")))
(set-pending-file (-> highest-buffer other) (the-as string #f) -1 (the-as handle #f) SPOOL_PRIORITY_LOWEST))))
;; update the buffers
(dotimes (i 2)
(update (-> this buffer i)))
;; Choose the animation stream to preload.
(let ((preload (the-as spool-anim #f)))
(countdown (i 3)
(if (and (-> this rec i name) (not (name= (-> this rec i name) (-> this active-stream))))
(set! preload (the-as spool-anim (-> this rec)))))
(if (and (-> this preload-stream name) (or (not preload) (< (-> this preload-stream priority) (-> preload priority))))
(set! preload (-> this preload-stream)))
(cond
(preload (mem-copy! (&-> this last-preload-stream type) (&-> preload type) 44) (str-play-queue (-> preload name)))
(else (set! (-> this last-preload-stream name) #f) (set! (-> this last-preload-stream owner) (the-as handle #f)))))
(when (and debug-print *display-art-control*)
(dotimes (i 3)
(format *stdcon*
"rec ~d ~S ~D ~f ~A~%"
i
(-> this rec i name)
(-> this rec i parts)
(-> this rec i priority)
(handle->name (-> this rec i owner))))
(dotimes (i 2)
(format *stdcon*
"buf ~d ~C ~S ~D ~A ~A~%"
i
(if (-> this buffer i locked?) #\l #\\s)
(-> this buffer i pending-load-file)
(-> this buffer i pending-load-file-part)
(-> this buffer i status)
(handle->name (-> this buffer i pending-load-file-owner))))
(format *stdcon* " a: ~S~%" (-> this active-stream))
(format *stdcon* " p: ~S ~A~%" (-> this preload-stream name) (handle->name (-> this preload-stream owner)))
(format *stdcon* " q: ~S ~A~%" (-> this last-preload-stream name) (handle->name (-> this last-preload-stream owner))))
0)
(defmethod none-reserved? ((this external-art-control))
"Return true when no general-use buffer reservation has been requested."
(declare (inline))
(zero? (-> this reserve-buffer-count)))
(defmethod reserve-alloc ((this external-art-control))
"Request one streaming buffer for general heap use. Return its heap after update has activated
the reserved pseudo-file; return #f while the reservation is pending."
(set! (-> this reserve-buffer-count) 1)
(if (-> this reserve-buffer) (-> this reserve-buffer heap)))
(defmethod reserve-free ((this external-art-control) (heap kheap))
"Release the reservation backed by heap. Clear the reserved buffer's pending request and
advance it immediately; print an error when no reservation exists or heap is not the reserved
one."
(cond
((none-reserved? this)
(format 0 "ERROR: illegal attempt to free a buffer #x~X which had not been reserved (none reserved).~%" heap))
((not (-> this reserve-buffer)) (set! (-> this reserve-buffer-count) 0))
((= (-> this reserve-buffer heap) heap)
(set-pending-file (-> this reserve-buffer) (the-as string #f) -1 (the-as handle #f) SPOOL_PRIORITY_LOWEST)
(update (-> this reserve-buffer))
(set! (-> this reserve-buffer-count) 0))
(else (format 0 "ERROR: illegal attempt to free a buffer #x~X which had not been reserved (buffer unknown).~%" heap)))
0)
(defmethod clear-rec ((this external-art-control))
"Clear streaming requests. In game mode clear all three ranked requests and the preload
candidate; outside game mode remove only the reserved pseudo-request and compact later records."
(cond
((!= *master-mode* 'game)
(dotimes (i 3)
(when (name= (-> this rec i name) "reserved")
(case i
((0)
(mem-copy! (&-> this rec 0 type) (&-> this rec 1 type) (size-of spool-anim))
(mem-copy! (&-> this rec 1 type) (&-> this rec 2 type) (size-of spool-anim)))
((1) (mem-copy! (&-> this rec 1 type) (&-> this rec 2 type) (size-of spool-anim))))
(set! (-> this rec 2 type) spool-anim)
(set! (-> this rec 2 name) #f)
(set! (-> this rec 2 priority) SPOOL_PRIORITY_LOWEST)
(set! (-> this rec 2 owner) (the-as handle #f)))))
(else
(dotimes (i 3)
(set! (-> this rec i type) spool-anim)
(set! (-> this rec i name) #f)
(set! (-> this rec i priority) SPOOL_PRIORITY_LOWEST)
(set! (-> this rec i owner) (the-as handle #f)))
(set! (-> this preload-stream type) spool-anim)
(set! (-> this preload-stream name) #f)
(set! (-> this preload-stream priority) SPOOL_PRIORITY_LOWEST)
(set! (-> this preload-stream owner) (the-as handle #f))))
0)
(defmacro spool-calc-priority (priority proc)
"Check the priority of a spool anim and recalculate it if needed. It will be the same as distance between target and the requesting process.
priority = var that stores priority
proc = a process to recalc from"
`(when (and (= ,priority SPOOL_PRIORITY_RECALC) ,proc)
(let ((target-trans (target-pos 0)))
(set! ,priority (vector-vector-distance target-trans (-> (the-as process-drawable ,proc) root trans))))))
(defmethod try-preload-stream ((this external-art-control) (name string) (part int) (requester process) (priority float))
"Make name and part the single speculative audio preload candidate when its priority is better
than the current candidate. SPOOL_PRIORITY_RECALC derives priority from requester distance."
(spool-calc-priority priority requester)
(unless (and (-> this preload-stream name) (>= priority (-> this preload-stream priority)))
(set! (-> this preload-stream name) name)
(set! (-> this preload-stream parts) part)
(set! (-> this preload-stream priority) priority)
(set! (-> this preload-stream owner) (process->handle requester)))
0)
(defmethod spool-push ((this external-art-control) (name string) (part int) (requester process) (priority float))
"Insert a file-part request into the three-entry priority list. Lower values are more important;
SPOOL_PRIORITY_RECALC uses the requester's distance from the target. An existing request is
replaced only by a better priority, and requests below the top three are dropped."
(spool-calc-priority priority requester)
;; if this spool-anim already exists, pop it from the rec list
(cond
((and (= part (-> this rec 0 parts)) (name= name (-> this rec 0 name)))
(if (>= priority (-> this rec 0 priority)) (return (the-as int #f)))
;; first spool. copy 1st <- 2nd and 2nd <- 3rd, 3rd will be wiped
(mem-copy! (&-> this rec 0 type) (&-> this rec 1 type) (size-of spool-anim))
(mem-copy! (&-> this rec 1 type) (&-> this rec 2 type) (size-of spool-anim))
(set! (-> this rec 2 name) #f)
(set! (-> this rec 2 owner) (the-as handle #f)))
((and (= part (-> this rec 1 parts)) (name= name (-> this rec 1 name)))
(if (>= priority (-> this rec 1 priority)) (return (the-as int #f)))
;; second spool. copy 2nd <- 3rd, 3rd will be wiped
(mem-copy! (&-> this rec 1 type) (&-> this rec 2 type) (size-of spool-anim))
(set! (-> this rec 2 name) #f)
(set! (-> this rec 2 owner) (the-as handle #f)))
((and (= part (-> this rec 2 parts)) (name= name (-> this rec 2 name)))
(if (>= priority (-> this rec 2 priority)) (return (the-as int #f)))
;; third spool. 3rd will be wiped
(set! (-> this rec 2 name) #f)
(set! (-> this rec 2 owner) (the-as handle #f))))
;; if it's top 3 priority, push this spool-anim to the rec list
(cond
((< priority (-> this rec 0 priority))
;; 1st place!
(mem-copy! (&-> this rec 2 type) (&-> this rec 1 type) (size-of spool-anim))
(mem-copy! (&-> this rec 1 type) (&-> this rec 0 type) (size-of spool-anim))
(set! (-> this rec 0 name) name)
(set! (-> this rec 0 parts) part)
(set! (-> this rec 0 priority) priority)
(set! (-> this rec 0 owner) (process->handle requester)))
((< priority (-> this rec 1 priority))
;; 2nd place.
(mem-copy! (&-> this rec 2 type) (&-> this rec 1 type) (size-of spool-anim))
(set! (-> this rec 1 name) name)
(set! (-> this rec 1 parts) part)
(set! (-> this rec 1 priority) priority)
(set! (-> this rec 1 owner) (process->handle requester)))
((< priority (-> this rec 2 priority))
;; 3rd place...
(set! (-> this rec 2 name) name)
(set! (-> this rec 2 parts) part)
(set! (-> this rec 2 priority) priority)
(set! (-> this rec 2 owner) (process->handle requester))))
0)
(defun-extern level-hint-surpress! none) ;; ? TODO
(define-extern ja-channel-push! (function int time-frame int :behavior process-drawable))
(define-extern ja-channel-set! (function int int :behavior process-drawable))
(define-extern joint-control-channel-group-eval!
(function joint-control-channel art-joint-anim (function joint-control-channel float float float) int))
(define-extern joint-control-channel-group!
(function joint-control-channel art-joint-anim (function joint-control-channel float float float) int))
(define-extern ja-aframe-num (function int float :behavior process-drawable))
(define-extern ja-abort-spooled-anim (function spool-anim art-joint-anim int int :behavior process-drawable))
(defbehavior ja-play-spooled-anim process-drawable ((request spool-anim)
(idle-anim art-joint-anim)
(exit-anim art-joint-anim)
(break-func (function process-drawable symbol)))
"Play every streamed part in request while driving the joint animation from the audio stream
position. Wait on the global spool lock using idle-anim, request the current part at highest
priority and the next part at high priority, execute timed load commands, and abort through
break-func or when audio fails to start. Cleanup always passes the last completed part and
exit-anim to ja-abort-spooled-anim."
(local-vars
(stream-pos int)
(spool-part int)
(part-audio-start float)
(old-skel-status janim-status)
(old-stream-pos int)
(good-time int)
(old-time int)
(good-count int)
(spool-sound sound-id))
(set! spool-part 0)
(set! part-audio-start -17.0)
(set! old-skel-status (-> self skel status))
(set! old-stream-pos -2)
(set! good-time 0)
(set! old-time 0)
(set! good-count 0)
(set! spool-sound (new-sound-id))
(backup-load-state-and-set-cmds *load-state* (-> request command-list))
(set-setting! 'spooling (process->ppointer self) 0.0 0)
(logior! (-> self skel status) (janim-status inited drawn done))
(kill-current-level-hint '() '() 'die)
(level-hint-surpress!)
(apply-settings *setting-control*)
(when (or (handle->process (-> *art-control* spool-lock)) (!= *master-mode* 'game))
(cond
(idle-anim (when (!= (ja-group) idle-anim) (ja-channel-push! 1 (seconds 0.05)) (ja :group! idle-anim :num! min)))
(else (ja-channel-set! 0)))
(while (or (handle->process (-> *art-control* spool-lock)) (!= *master-mode* 'game))
(format #t "WARNING: ---------------------> loader stall on lock~%")
(if (break-func self) (goto cfg-88))
(spool-push *art-control* (-> request name) spool-part self -9.0)
(suspend)
(if idle-anim (ja :num! (loop!)))))
(set! (-> *art-control* spool-lock) (process->handle self))
(set! old-time (the-as int (current-time)))
(while (< spool-part (-> request parts))
(spool-push *art-control* (-> request name) spool-part self SPOOL_PRIORITY_HIGHEST)
(update *art-control* #f)
(spool-push *art-control* (-> request name) spool-part self SPOOL_PRIORITY_HIGHEST)
(when (!= (file-status *art-control* (-> request name) spool-part) 'active)
(cond
(idle-anim (when (!= (ja-group) idle-anim) (ja-channel-set! 1) (ja :group! idle-anim :num! min)))
(else (ja-channel-set! 0)))
(while (!= (file-status *art-control* (-> request name) spool-part) 'active)
(if (break-func self) (goto cfg-88))
(spool-push *art-control* (-> request name) spool-part self SPOOL_PRIORITY_HIGHEST)
(format #t "WARNING: ---------------------> loader stall on art ~S ~D~%" (-> request name) spool-part)
(suspend)
(if idle-anim (ja :num! (loop!)))))
(spool-push *art-control* (-> request name) spool-part self SPOOL_PRIORITY_HIGHEST)
(let ((loaded-anim (the-as art-joint-anim (lookup-art (-> self draw art-group) (-> request name) art-joint-anim))))
(cond
(loaded-anim
(ja-channel-set! 1)
(ja-no-eval :group! loaded-anim :num! (seek!) :frame-num 0.0)
(when (zero? spool-part)
(str-play-async (-> request name) spool-sound)
(set! (-> *art-control* active-stream) (-> request name)))
;; Convert STR positions to animation frames. Each part begins where the preceding part's
;; audio range ended, so the animation remains continuous across streamed chunks.
(let* ((frames-per-stream-unit (* 0.05859375 (-> loaded-anim speed)))
(part-audio-end (+ part-audio-start (/ (the float (+ (-> loaded-anim data 0 length) -1)) frames-per-stream-unit))))
(set! stream-pos (current-str-pos spool-sound))
(set! good-time (the-as int (current-time)))
(until (>= (the float stream-pos) part-audio-end)
(if (= (-> self skel root-channel 0) (-> self skel channel)) (logior! (-> self skel status) (janim-status spool)))
(if (or (break-func self)
(and (<= stream-pos 0) (time-elapsed? (the-as time-frame good-time) (seconds 4)))
(and (< 300 good-count) (<= stream-pos 0)))
(goto cfg-88))
(spool-push *art-control* (-> request name) spool-part self SPOOL_PRIORITY_HIGHEST)
(if (< (+ spool-part 1) (-> request parts))
(spool-push *art-control* (-> request name) (+ spool-part 1) self SPOOL_PRIORITY_HIGH)
(logclear! (-> self skel status) (janim-status done)))
(execute-commands-up-to *load-state* (ja-aframe-num 0))
(cond
((and (< old-stream-pos stream-pos) (= (current-str-id) spool-sound))
;; Use elapsed 300 Hz ticks for this display frame so high frame rates do not
;; advance the startup counter too quickly.
(+! good-count (- (current-time) (-> *display* old-base-frame-counter)))
(set! good-time (the-as int (current-time))))
(else 0))
(set! old-stream-pos stream-pos)
(set! old-time (the-as int (current-time)))
(suspend)
(let ((anim-frame (* (- (the float (current-str-pos spool-sound)) part-audio-start) frames-per-stream-unit)))
(ja-no-eval :num! (seek!) :frame-num anim-frame))
(set! stream-pos (current-str-pos spool-sound)))
(set! part-audio-start part-audio-end))
(logclear! (-> self skel status) (janim-status spool)))
(else
;; og:preserve-this fixed broken format string
(format 0 "ERROR: <asg> ~A in spool anim loop for ~A ~D, but not loaded.~%" self (-> request name) spool-part)
(goto cfg-88))))
(+! spool-part 1))
(+! spool-part -1)
(label cfg-88)
(ja-abort-spooled-anim request exit-anim spool-part)
0)
(defbehavior ja-abort-spooled-anim process-drawable ((request spool-anim) (exit-anim art-joint-anim) (completed-part int))
"Finish or abort a spooled animation. Restore loader state, stop its stream, clear spooling
status, and release the spool lock. If exit-anim is supplied and completed-part is nonnegative,
blend onto that animation while keeping the completed part requested until the channel switch
finishes."
(restore-load-state-and-cleanup *load-state*)
(str-play-stop (-> request name))
(set! (-> *art-control* active-stream) #f)
(logclear! (-> self skel status) (janim-status drawn done))
(if (not (logtest? (-> self skel status) (janim-status inited))) (logclear! (-> self skel status) (janim-status inited)))
(remove-setting! 'spooling)
(cond
((and exit-anim (>= completed-part 0))
(ja-channel-push! 1 (seconds 0.1))
(set! (-> self skel root-channel 0 frame-group) exit-anim)
(while (!= (-> self skel root-channel 0) (-> self skel channel))
(spool-push *art-control* (-> request name) completed-part self SPOOL_PRIORITY_HIGHEST)
(suspend)
(ja :num! (seek!))))
(else (ja-channel-set! 0)))
(set! (-> *art-control* spool-lock) (the-as handle #f))
0)
(if (zero? *art-control*) (set! *art-control* (new 'global 'external-art-control)))