104 lines
4.8 KiB
Plaintext
104 lines
4.8 KiB
Plaintext
;;;; (Handle T) and (Pool T), spec-memory.md — "Cross-referencing long-lived
|
|
;;;; objects uses (Handle a) into a pool, never a raw pointer or slice. A
|
|
;;;; stale handle is detectable."
|
|
;;;;
|
|
;;;; The thesis, in one program: something holds a reference to an entity; the
|
|
;;;; entity dies; the slot is reused by a different entity; and the old
|
|
;;;; reference answers "gone" instead of answering wrong. Every other case
|
|
;;;; here is secondary to that one.
|
|
;;;;
|
|
;;;; It is all one function because a Pool is move-only exactly as a Vec is,
|
|
;;;; so passing one to a helper *consumes* it — there is no borrowing
|
|
;;;; parameter in the language yet. That is not a pool question and this
|
|
;;;; program does not work around it; see docs/BUILT.md.
|
|
|
|
(defstruct Enemy [hp i32 kind i32])
|
|
|
|
;; The projectile does not hold an Enemy and does not hold an index. It holds
|
|
;; a handle, which is a number that owns nothing and copies freely — which is
|
|
;; why a struct may contain one where it may not contain a Vec.
|
|
(defstruct Projectile [target (Handle Enemy) damage i32])
|
|
|
|
(defn main [] i32
|
|
(let [pool (pool-new Enemy)]
|
|
(let [a (insert pool (Enemy {.hp 10 .kind 1}))
|
|
b (insert pool (Enemy {.hp 20 .kind 2}))
|
|
c (insert pool (Enemy {.hp 30 .kind 3}))
|
|
sum 0]
|
|
(println (len pool)) ; 3 slots handed out
|
|
(println (live pool)) ; 3 of them live
|
|
|
|
;; Enumeration, which is what a world arena and an owned region do not
|
|
;; give and which migrate-instances will need. (len p) is the slot
|
|
;; high-water, so 0..(len p) visits every slot ever handed out, and
|
|
;; (pool-handle p i) says which of them are still live.
|
|
(dotimes [i (len pool)]
|
|
(match (pool-handle pool i)
|
|
(Some h) (match (resolve pool h)
|
|
;; resolve yields a *pointer*, not a copy: mutating the
|
|
;; pooled thing in place is what a pool is for, and a
|
|
;; pattern binding binds a value.
|
|
(Some e) (set sum (+ sum (.hp e)))
|
|
None (do))
|
|
None (do)))
|
|
(println sum) ; 60
|
|
|
|
;; A write through a resolved pointer is a write to the pooled entity.
|
|
(match (resolve pool b)
|
|
(Some e) (set (.hp e) 21)
|
|
None (do))
|
|
(match (resolve pool b)
|
|
(Some e) (println (.hp e)) ; 21
|
|
None (println -1))
|
|
|
|
;; ── The thesis ────────────────────────────────────────────────
|
|
;; A projectile chasing b. b dies. The slot is reused by a fourth
|
|
;; enemy, which lands in exactly that slot — and the projectile's
|
|
;; handle says so rather than chasing the newcomer.
|
|
(let [shot (Projectile {.target b .damage 5})]
|
|
(println (release pool b)) ; true — this call released it
|
|
(println (release pool b)) ; false — it was already gone
|
|
(println (live pool)) ; 2
|
|
(let [d (insert pool (Enemy {.hp 99 .kind 4}))]
|
|
;; Printed as index:generation. Same slot, later generation — the
|
|
;; two halves of the answer, visible.
|
|
(println b)
|
|
(println d)
|
|
(println (= d b)) ; false
|
|
(println (= d d)) ; true
|
|
(match (resolve pool (.target shot))
|
|
(Some e) (println (.hp e))
|
|
None (println -1)) ; -1, not 99
|
|
(match (resolve pool d)
|
|
(Some e) (println (.hp e)) ; 99
|
|
None (println -1))
|
|
(println (len pool)) ; still 3 slots
|
|
(println (live pool)) ; 3 live
|
|
|
|
;; A zeroed handle is generation 0, which is even, and a live slot's
|
|
;; generation is always odd — so ZII gives a handle field the right
|
|
;; meaning for free rather than pointing it at slot 0.
|
|
(let [z (Projectile {.damage 1})]
|
|
(println (.target z))
|
|
(match (resolve pool (.target z))
|
|
(Some e) (println (.hp e))
|
|
None (println -1))) ; -1
|
|
|
|
;; a and c are untouched by any of it.
|
|
(match (resolve pool a)
|
|
(Some e) (println (.hp e)) ; 10
|
|
None (println -1))
|
|
(match (resolve pool c)
|
|
(Some e) (println (.hp e)) ; 30
|
|
None (println -1))
|
|
|
|
;; spec-memory.md's first release point, applied to the owner. The
|
|
;; runtime leaves the pool empty, so a handle into it would resolve
|
|
;; to None rather than into released storage — but that is not
|
|
;; demonstrable from here and this program does not pretend it is:
|
|
;; free consumes pool, so a resolve on the next line is a compile
|
|
;; error. The runtime property is real and the checker makes it
|
|
;; unreachable.
|
|
(free pool)
|
|
0)))))
|