5 Resources
dxtr edited this page 2026-10-20 10:09:08 +02:00

Resources

The system pyssel/resource holds resources: data kept in files of their own or inside the files that use them, loaded once and shared, as Godot's resources are. define-resource defines a resource class, whose properties work as nodes' do, and save-resource and load-resource save and load one, here the arena's stats of Saving Scenes:

(res:save-resource (make-instance 'stats :health 6) "brute.resource")
(stats-health (res:load-resource "brute.resource")) ; => 6

A resource that has a file of its own, its resource-path, is saved as a reference to that file, relative to the file that refers to it; one that has none is kept inside, once however many properties hold it, as stats is in the arena.scene of Saving Scenes. load-resource gives the same object for the same file while anything holds it, unless something else wrote the file since. Each session has its own cache, so a game played in the editor's image loads copies of its own, and what it changes in them stays its own. A packed scene is a resource too, so a scene that many levels hold is loaded once.

A value class, which define-value-class defines, makes objects with properties that are kept inside the files of whatever holds them, by value, as a menu's items are: each is saved as (:object (class initarg value...)), with the properties that differ from a new one's, and has no file of its own, so loading makes every holder an object of its own.

resource-changed-p tells whether a resource holds what its file does, comparing what saving would write with the file's form, so how the file is laid out does not count; resource-file-changed-p whether something else wrote the file since the resource was loaded or saved, which saving would overwrite; and revert-resource reads the file into the same object again, so whatever holds it sees the file's values. The editor saves and throws away changes to resources so:

(let ((brute (res:load-resource "brute.resource")))
  (setf (stats-health brute) 99)
  (list (res:resource-changed-p brute)         ; => T
        (stats-health (res:revert-resource brute))  ; => 6
        (res:resource-changed-p brute)))       ; => NIL

Files can move under a running editor, and it keeps them referring to each other, as the File System dock does: file-references lists the files a file of Pyssel's format refers to, absolute, reading its form without loading it; retarget-file-references rewrites the paths a file refers to others by, given a function that returns where each file is now, and, with :from, where the file itself was before it moved, each path written relative to the file as saving writes it; resource-file-moved has the resources loaded from a file, or from the files under a folder, follow them to where they are, the same objects, which loading their new paths gives; and resource-file-deleted lets them go of their files, so whatever holds them keeps them inside its own file when it is saved.

property-value reads a property of a node or a resource by its name, as class-properties gives it, and setf of it sets one as code would, through the class's own setter, which checks the value and acts on it, as a node draws itself again: the inspector and the editor's histories work so. It takes any real for a float property, as make-instance and loading a file do. A class whose property holds an object it changes in place, as a 2D node's position vector, gives a copy, so a value read stays as it was; one whose property is made when asked for, as a text edit's text from its lines, makes it. Saving and reverting read properties through property-value too. property-type gives a property's type as its class declared it, and property-hint its hint.

A resource class checks what make-instance's initargs give its properties against their types, as a node class does (see Scene Tree), in words read from the types, and its slots take :copy too, to keep a copy of what an initarg gives them.

A slot of a float type takes :cell t, to keep its value in a res:cell, an array of one float, changed in place, so that a double-float that changes each frame changes without allocating: Lisp keeps a double-float in a slot as an object of its own, made anew each time the slot is set. The slot's readers return the cell, and property-value reads and sets the float in it; the class reads and sets it with inline functions of its own, as a 2D node's node-rotation does:

(res:define-object spinner ()
  ((angle :initarg :angle :initform 0d0 :type double-float :cell t
          :reader angle-cell :property t)))

(declaim (inline spinner-angle (setf spinner-angle)))
(defun spinner-angle (spinner)
  (aref (the (res:cell double-float) (angle-cell spinner)) 0))
(defun (setf spinner-angle) (angle spinner)
  (setf (aref (the (res:cell double-float) (angle-cell spinner)) 0)
        (float angle 1d0)))

A subclass that gives such a slot a type or an initform says :cell t too. A tween of the property allocates nothing when the class gives it the cell with property-place.

A resource has signals, which its class names with (:signals ...), as Godot's ButtonGroup has pressed.

duplicate-resource copies a resource, as Godot's duplicate and duplicate_deep do: a new resource of its class, with no file, made from its properties. A shallow copy, the default, holds the same lists, vectors, value objects and resources; :deep t copies them and what they hold, the resources among them as :subresources says: :internal, the default, those with no file of their own, :all every one, :none none. Each is copied once, so two properties that held one hold one copy:

(let* ((brute (res:load-resource "brute.resource"))
       (stronger (res:duplicate-resource brute)))
  (setf (stats-health stronger) 12)
  (list (stats-health brute) (res:resource-path stronger)))  ; => (6 NIL)

Files of other formats are resources too, read by the methods of read-resource-file for their type, cached the same way, and only loaded: a resource that holds one refers to its file. The system pyssel/font loads font files, .ttf, .otf and .ttc, as font-file resources, and font-at gives one at a size for drawing text, as Godot's FontFile does. A font-variation draws a variable font with values of its axes, such as a weight, and is saved as resources are:

(asdf:load-system :pyssel/font)
;; In a package with (#:pf #:pyssel/font) among its local nicknames:

(res:save-resource (make-instance 'pf:font-variation :base (res:load-resource "fonts/Inter.ttf")
                                                     :variations '(("wght" . 700)))
                   "fonts/inter-bold.resource")

(defun title (canvas)                   ; a canvas's draw function
  (p2d:draw-text canvas "Title" 10 10
                 (pf:font-at (res:load-resource "fonts/inter-bold.resource") 20) '(1 1 1)))

The variation is a resource of its own, so the canvas asks for it by its file, and loading it again gives the same object while anything holds it. A font file is read into memory once. Its foreign objects and what the renderers made from its glyphs are freed once nothing holds the font, its sizes, or a canvas that draws it.