new! object instantiation macro (resolves #10)

This commit is contained in:
~keith 2022-02-23 23:31:50 +00:00
parent 38447b2c7e
commit 8d70a235db
Signed by: keith
GPG Key ID: 5BEBEEAB2C73D520
6 changed files with 275 additions and 211 deletions

View File

@ -10,11 +10,13 @@
:components :components
((:module "wh-engine" ((:module "wh-engine"
:components ((:file "package") :components ((:file "package")
(:file "main") (:file "global")
(:file "serialization") (:file "serialization")
(:file "actor") (:file "actor")
(:file "component") (:file "component")
(:file "actor-macros")
(:file "scene") (:file "scene")
(:file "render/drawable") (:file "render/drawable")
(:file "render/view")) (:file "render/view")
(:file "main"))
))) )))

View File

@ -0,0 +1,66 @@
;;;; wh-engine/actor-macros.lisp
(in-package wh-engine)
(defvar *new!-impl-alist* ()
"Alist of special implementations for new!")
(defmacro define-new!-impl (class (class-var it-var params-var) &body body)
"Define a special implementation for new!, which returns (values other-forms make-params)."
`(push (cons (find-class ',class)
(lambda (,class-var ,it-var ,params-var) ,@body))
*new!-impl-alist*))
(defmacro new! (class &rest params &key &allow-other-keys)
"Create a new instance of class, as specified by params."
(let ((impl (cdr (assoc-if (lambda (x) (subtypep x class)) *new!-impl-alist*)))
(it (gensym)))
(if impl
(multiple-value-bind (other-forms make-params)
(funcall impl class it params)
`(let ((,it (make-instance ',class ,@make-params))) ,@other-forms ,it))
`(make-instance ',class ,@params))
))
(define-new!-impl actor (class it params)
(declare (ignore class))
(loop for (key value) on params by #'cddr
if (eq key :component)
collect `(o! ,it (add-component ,value)) into other-forms
else
if (eq key :child)
collect `(o! ,it (add-child ,value)) into other-forms
else
if (eq key :parent)
collect `(o! ,value (add-child ,it)) into other-forms
else
nconc `(,key ,value) into make-params
finally (return (values other-forms make-params))))
#|
(define-new!-impl component (class it params)
(loop for (key value) on params by #'cddr
if (eq key :actor)
collect `(o! ,value (add-component ,it)) into other-forms
else
nconc `(,key ,value) into make-params
finally (return (values other-forms make-params))))
|#
#|
(defmacro actor! (class &rest params &key &allow-other-keys)
(let* ((cons-form `(make-instance ',class))
(it (gensym))
(other-forms
(loop for (key value) on params by #'cddr
if (eq key :component)
collect `(o! ,it (add-component ,value))
else
if (eq key :child)
collect `(o! ,it (add-child ,value))
else
do (nconc cons-form `(,key ,value)))))
`(let ((,it ,cons-form)) ,@other-forms ,it)))
(defmacro component! (class &rest params &key &allow-other-keys)
`(make-instance ',class ,@params))
|#

157
wh-engine/global.lisp Normal file
View File

@ -0,0 +1,157 @@
;;;; wh-engine/global.lisp
;;;; global variables and utility functions
(in-package wh-engine)
;; FIXME this should be a defconst
(defvar +version+ (list 0 1 0)
"Engine version.")
(defun ensure-version (expected-version)
"Ensure this version of wh-engine is compatible with the expected version."
(if (= (nth 0 +version+) 0)
(unless (equal +version+ expected-version)
(error "engine version ~S incompatible with expected-version ~S (pre-1.0 mismatch)"
+version+ expected-version))
(progn
(unless (= (nth 0 +version+) (nth 0 expected-version))
(error "engine version ~S incompatible with expected-version ~S (major-version mismatch)"
+version+ expected-version))
(unless (>= (nth 1 +version+) (nth 1 expected-version))
(error "engine version ~S incompatible with expected-version ~S (minor-version too low)"
+version+ expected-version))
(unless (equal +version+ expected-version)
(warn "engine version ~S differs from expected-version ~S, but is still compatible"
+version+ expected-version)))))
(declaim (inline deref-pointer))
(defun deref-pointer (ptr)
"Dereference ptr if it's non-nil."
(declare (type (or weak-pointer null) ptr))
(when ptr (weak-pointer-value ptr)))
(defun points-to (ptr obj)
"Return true if ptr points to obj."
(declare (type weak-pointer ptr))
(eq (weak-pointer-value ptr) obj))
(defun ensure-live (obj)
"Ensure obj is live (non-destroyed)."
(when (o! obj destroyed-p)
(error "~S was used after it was destroyed" obj))
obj)
(declaim (inline vxy1))
(defun vxy1 (vector)
"Convert vector to a vec3, with a Z component of 1."
(declare (type vec2 vector))
(vec3 (vx2 vector) (vy2 vector) 1))
(declaim (inline vxy-trunc))
(defun vxy-trunc (vector)
"Convert vector to a vec2 and truncate its X and Y components to the nearest integer."
(declare (type vec3 vector))
(vec2 (ftruncate (vx3 vector)) (ftruncate (vy3 vector))))
(defun opengl-matrix (matrix)
(declare (type mat3 matrix))
(with-fast-matref (m matrix 3)
(make-array '(16) :initial-contents
`(,(m 0 0) ,(m 0 1) 0.0 ,(m 0 2)
,(m 1 0) ,(m 1 1) 0.0 ,(m 1 2)
0.0 0.0 1.0 0.0
,(m 2 0) ,(m 2 1) 0.0 ,(m 2 2))
)))
(defvar *id-counter* 0
"Counter for assigning unique IDs.")
(defun make-id ()
"Return a unique ID."
(setf *id-counter* (+ *id-counter* 1)))
(defun fixed-id (id)
"Ensure the given ID won't be returned by make-id."
(declare (type fixnum id))
(when (>= id *id-counter*)
(setf *id-counter* (+ id 1)))
id)
(defstruct id-ref
(scene 0 :type fixnum)
(actor nil :type (or fixnum null))
(component nil :type (or symbol null)))
(deftype pointer () '(or id-ref weak-pointer null))
(declaim (inline deref-sus-pointer))
(defun deref-sus-pointer (val)
"Dereference val, and warn if it's suspended."
(declare (type pointer val))
(etypecase val
(weak-pointer (weak-pointer-value val))
(id-ref (warn "dereferencing sus pointer ~S" val)
(weak-pointer-value (pointerize val)))
(null nil)))
(defun referize (ptr)
"Convert ptr into an id-ref."
(let ((target (etypecase ptr
(weak-pointer (weak-pointer-value ptr))
((or scene actor component) ptr))))
(etypecase target
(scene
(make-id-ref :scene (o! target id)))
(actor
(make-id-ref :scene (etypecase (o! target :slot scene)
(weak-pointer (o! target scene id))
(id-ref (id-ref-scene (o! target :slot scene))))
:actor (o! target id)))
(component
(make-id-ref :scene (etypecase (o! target :slot actor)
(weak-pointer (o! target scene id))
(id-ref (id-ref-scene (o! target :slot actor))))
:actor (etypecase (o! target :slot actor)
(weak-pointer (o! target actor id))
(id-ref (id-ref-actor (o! target :slot actor))))
:component (class-name (class-of target))))
)))
(defun dereferize (ref)
"Return the object specified by id-ref ref."
(declare (type id-ref ref))
(let ((scene (get-scene (id-ref-scene ref))) actor component)
(unless scene
(error "can't dereferize ~S (scene not found)" ref))
(if (id-ref-actor ref)
(if (setf actor (o! scene (get-actor (id-ref-actor ref))))
(if (id-ref-component ref)
(if (setf component (o! actor (get-component (find-class (id-ref-component ref)))))
component
(error "can't dereferize ~S (component not found)" ref))
actor)
(error "can't dereferize ~S (actor not found)" ref))
scene)))
(defun pointerize (ref)
"Convert id-ref ref into a weak-pointer."
(declare (type id-ref ref))
(make-weak-pointer (dereferize ref)))
(defmacro referize-setf (place)
`(when (typep ,place 'weak-pointer)
(setf ,place (referize ,place))))
(defmacro pointerize-setf (place)
`(when (typep ,place 'id-ref)
(setf ,place (pointerize ,place))))
(defvar *world-scenes* ()
"List of all running scenes.")
(defun get-scene (scene-id)
"Get a scene by its ID."
(find-if (lambda (scene) (eq (o! scene id) scene-id)) *world-scenes*))

View File

@ -1,85 +1,6 @@
;;;; wh-engine/main.lisp ;;;; wh-engine/main.lisp
(in-package wh-engine) (in-package wh-engine)
;; FIXME this should be a defconst
(defvar +version+ (list 0 1 0)
"Engine version.")
(defun ensure-version (expected-version)
"Ensure this version of wh-engine is compatible with the expected version."
(if (= (nth 0 +version+) 0)
(unless (equal +version+ expected-version)
(error "engine version ~S incompatible with expected-version ~S (pre-1.0 mismatch)"
+version+ expected-version))
(progn
(unless (= (nth 0 +version+) (nth 0 expected-version))
(error "engine version ~S incompatible with expected-version ~S (major-version mismatch)"
+version+ expected-version))
(unless (>= (nth 1 +version+) (nth 1 expected-version))
(error "engine version ~S incompatible with expected-version ~S (minor-version too low)"
+version+ expected-version))
(unless (equal +version+ expected-version)
(warn "engine version ~S differs from expected-version ~S, but is still compatible"
+version+ expected-version)))))
(declaim (inline deref-pointer))
(defun deref-pointer (ptr)
"Dereference ptr if it's non-nil."
(declare (type (or weak-pointer null) ptr))
(when ptr (weak-pointer-value ptr)))
(defun points-to (ptr obj)
"Return true if ptr points to obj."
(declare (type weak-pointer ptr))
(eq (weak-pointer-value ptr) obj))
(defun ensure-live (obj)
"Ensure obj is live (non-destroyed)."
(when (o! obj destroyed-p)
(error "~S was used after it was destroyed" obj))
obj)
(declaim (inline vxy1))
(defun vxy1 (vector)
"Convert vector to a vec3, with a Z component of 1."
(declare (type vec2 vector))
(vec3 (vx2 vector) (vy2 vector) 1))
(declaim (inline vxy-trunc))
(defun vxy-trunc (vector)
"Convert vector to a vec2 and truncate its X and Y components to the nearest integer."
(declare (type vec3 vector))
(vec2 (ftruncate (vx3 vector)) (ftruncate (vy3 vector))))
(defun opengl-matrix (matrix)
(declare (type mat3 matrix))
(with-fast-matref (m matrix 3)
(make-array '(16) :initial-contents
`(,(m 0 0) ,(m 0 1) 0.0 ,(m 0 2)
,(m 1 0) ,(m 1 1) 0.0 ,(m 1 2)
0.0 0.0 1.0 0.0
,(m 2 0) ,(m 2 1) 0.0 ,(m 2 2))
)))
(defvar *id-counter* 0
"Counter for assigning unique IDs.")
(defun make-id ()
"Return a unique ID."
(setf *id-counter* (+ *id-counter* 1)))
(defun fixed-id (id)
"Ensure the given ID won't be returned by make-id."
(declare (type fixnum id))
(when (>= id *id-counter*)
(setf *id-counter* (+ id 1)))
id)
(defvar *world-scenes* ()
"List of all running scenes.")
(defun add-scene (scene) (defun add-scene (scene)
"Add a scene to the list of running scenes." "Add a scene to the list of running scenes."
(declare (type scene scene)) (declare (type scene scene))
@ -94,10 +15,6 @@
(setf *world-scenes* (remove scene *world-scenes*)) (setf *world-scenes* (remove scene *world-scenes*))
scene) scene)
(defun get-scene (scene-id)
"Get a scene by its ID."
(find-if (lambda (scene) (eq (o! scene id) scene-id)) *world-scenes*))
(defun initialize-actors-in (scene &rest actors) (defun initialize-actors-in (scene &rest actors)
"Properly attach actors and their descendents to scene, and initialize them." "Properly attach actors and their descendents to scene, and initialize them."
(loop for actor in actors (loop for actor in actors
@ -127,62 +44,36 @@
(defvar *world-views* nil (defvar *world-views* nil
"List of all known views.") "List of all known views.")
(defvar *delta-time* 0.0
"Time in seconds since the last game tick.")
(defun register-test-scene () (defun register-test-scene ()
(let (test-scene test-actor test-drawable test-actor-2 camera-actor camera-view child-actor grandchild-actor) (let ((test-scene (new! scene :id -1 :name "Test scene")))
(setf test-scene (make-instance 'scene
:id -1
:name "Test scene"))
(add-scene test-scene) (add-scene test-scene)
(setf test-actor (make-instance 'actor
:name "Actor"))
;; (o! test-scene (add-actor test-actor))
(setf test-drawable (make-instance 'drawable-test))
(o! test-actor (add-component test-drawable))
(setf test-actor-2 (make-instance 'actor
:name "Actor 2"
:location (vec2 0.5 0.5)
:rotation (coerce (/ pi 4) 'single-float)
:z-layer -1))
;; (o! test-scene (add-actor test-actor-2))
(o! test-actor-2 (add-component (make-instance 'drawable-test
:colour (vec4 0.0 1.0 0.0 1.0))))
(setf child-actor (make-instance 'actor
:name "Child Actor"
:location (vec2 0 0.5)
:z-layer -2))
;; (o! test-scene (add-actor child-actor))
(o! test-actor-2 (add-child child-actor))
(o! child-actor (add-component (make-instance 'drawable-test
:colour (vec4 0.0 1.0 1.0 1.0))))
(setf grandchild-actor (make-instance 'actor
:name "Grandchild Actor"
:location (vec2 0 1)
:scale (vec2 0.25 0.25)
:z-layer 1))
;; (o! test-scene (add-actor grandchild-actor))
(o! child-actor (add-child grandchild-actor))
(o! grandchild-actor (add-component (make-instance 'drawable-test
:colour (vec4 1.0 1.0 0.0 0.0))))
(setf camera-actor (make-instance 'actor
:name "Camera"))
;; (o! test-scene (add-actor camera-actor))
(setf camera-view (make-instance 'view))
(o! camera-actor (add-component camera-view))
;;(sb-ext:gc)
(initialize-actors-in test-scene (initialize-actors-in test-scene
test-actor test-actor-2 camera-actor) (new! actor
:name "Actor"
:component (new! drawable-test))
(new! actor
:name "Actor 2"
:location (vec2 0.5 0.5)
:rotation (coerce (/ pi 4) 'single-float)
:z-layer -1
:component (new! drawable-test :colour (vec4 0 1 0 1))
:child (new! actor
:name "Child Actor"
:location (vec2 0 0.5)
:z-layer -2
:component (new! drawable-test :colour (vec4 0 1 1 1))
:child (new! actor
:name "Grandchild Actor"
:location (vec2 0 1)
:scale (vec2 0.25 0.25)
:z-layer 1
:component (new! drawable-test :colour (vec4 1 1 0 1)))))
(new! actor
:name "Camera"
:component (new! view)))
test-scene)) test-scene))
(defun run () (defun run ()
@ -203,7 +94,11 @@
(renderbuf (gl:gen-renderbuffer)) (renderbuf (gl:gen-renderbuffer))
(render-texture (gl:gen-texture)) (render-texture (gl:gen-texture))
(win-width (nth-value 0 (sdl2:get-window-size win))) (win-width (nth-value 0 (sdl2:get-window-size win)))
(win-height (nth-value 1 (sdl2:get-window-size win)))) (win-height (nth-value 1 (sdl2:get-window-size win)))
(prev-tick (sdl2:get-ticks))
(this-tick (sdl2:get-ticks))
(prev-profiling-tick (sdl2:get-performance-counter))
(profiling-scale (/ (sdl2:get-performance-frequency) 1000.0)))
;; set up framebuffer ;; set up framebuffer
(gl:bind-framebuffer :framebuffer framebuf) (gl:bind-framebuffer :framebuffer framebuf)
@ -244,9 +139,16 @@
(sdl2:with-event-loop (:method :poll) (sdl2:with-event-loop (:method :poll)
(:quit () t) (:quit () t)
(:idle () (:idle ()
(setf prev-profiling-tick (sdl2:get-performance-counter))
;; calculate delta-time
(setf this-tick (sdl2:get-ticks))
(setf *delta-time* (* (- this-tick prev-tick) 0.001))
(setf prev-tick this-tick)
(format t "Δt = ~S (~S FPS)~%" *delta-time* (/ 1.0 *delta-time*))
;; update ;; update
(loop for scene in *world-scenes* (loop for scene in *world-scenes*
do (o! scene (update))) do (o! scene (update)))
(format t "game=~S " (/ (- (sdl2:get-performance-counter) prev-profiling-tick) profiling-scale))
;; draw to render texture ;; draw to render texture
(gl:bind-framebuffer :framebuffer framebuf) (gl:bind-framebuffer :framebuffer framebuf)
(gl:viewport 0 0 *view-width* *view-height*) (gl:viewport 0 0 *view-width* *view-height*)
@ -261,6 +163,8 @@
(setf render-pass (o! view render-pass)) (setf render-pass (o! view render-pass))
(gl:clear :depth-buffer)) (gl:clear :depth-buffer))
(o! view (render-view *world-drawables*))))) (o! view (render-view *world-drawables*)))))
(gl:flush)
(format t "draw=~S " (/ (- (sdl2:get-performance-counter) prev-profiling-tick) profiling-scale))
;; now draw to window ;; now draw to window
(gl:bind-framebuffer :framebuffer 0) (gl:bind-framebuffer :framebuffer 0)
@ -272,7 +176,7 @@
(gl:matrix-mode :modelview) (gl:matrix-mode :modelview)
(gl:load-identity) (gl:load-identity)
(gl:with-primitive :quads (gl:with-primitives :quads
(gl:color 1.0 1.0 1.0 1.0) (gl:color 1.0 1.0 1.0 1.0)
(gl:tex-coord 0.0 0.0) (gl:tex-coord 0.0 0.0)
(gl:vertex 0.0 0.0) (gl:vertex 0.0 0.0)
@ -286,5 +190,6 @@
(gl:disable :texture-2d) (gl:disable :texture-2d)
(gl:flush) (gl:flush)
(format t "blit=~S~%" (/ (- (sdl2:get-performance-counter) prev-profiling-tick) profiling-scale))
(sdl2:gl-swap-window win))) (sdl2:gl-swap-window win)))
))))) )))))

View File

@ -19,6 +19,7 @@
*view-width* *view-height* *view-ppu* *pixel-scale* *view-width* *view-height* *view-ppu* *pixel-scale*
register-test-scene register-test-scene
run run
*delta-time*
;; serialization.lisp ;; serialization.lisp
id-ref make-id-ref id-ref make-id-ref
@ -68,6 +69,10 @@
start update start update
destroy destroy
;; actor-macros.lisp
new!
define-new!-impl
;; scene.lisp ;; scene.lisp
scene scene
; properties ; properties

View File

@ -1,77 +1,6 @@
;;;; wh-engine/serialization.lisp ;;;; wh-engine/serialization.lisp
(in-package wh-engine) (in-package wh-engine)
(defstruct id-ref
(scene 0 :type fixnum)
(actor nil :type (or fixnum null))
(component nil :type (or symbol null)))
(deftype pointer () '(or id-ref weak-pointer null))
(declaim (inline deref-sus-pointer))
(defun deref-sus-pointer (val)
"Dereference val, and warn if it's suspended."
(declare (type pointer val))
(etypecase val
(weak-pointer (weak-pointer-value val))
(id-ref (warn "dereferencing sus pointer ~S" val)
(weak-pointer-value (pointerize val)))
(null nil)))
(defun referize (ptr)
"Convert ptr into an id-ref."
(let ((target (etypecase ptr
(weak-pointer (weak-pointer-value ptr))
((or scene actor component) ptr))))
(etypecase target
(scene
(make-id-ref :scene (o! target id)))
(actor
(make-id-ref :scene (etypecase (o! target :slot scene)
(weak-pointer (o! target scene id))
(id-ref (id-ref-scene (o! target :slot scene))))
:actor (o! target id)))
(component
(make-id-ref :scene (etypecase (o! target :slot actor)
(weak-pointer (o! target scene id))
(id-ref (id-ref-scene (o! target :slot actor))))
:actor (etypecase (o! target :slot actor)
(weak-pointer (o! target actor id))
(id-ref (id-ref-actor (o! target :slot actor))))
:component (class-name (class-of target))))
)))
(defun dereferize (ref)
"Return the object specified by id-ref ref."
(declare (type id-ref ref))
(let ((scene (get-scene (id-ref-scene ref))) actor component)
(unless scene
(error "can't dereferize ~S (scene not found)" ref))
(if (id-ref-actor ref)
(if (setf actor (o! scene (get-actor (id-ref-actor ref))))
(if (id-ref-component ref)
(if (setf component (o! actor (get-component (find-class (id-ref-component ref)))))
component
(error "can't dereferize ~S (component not found)" ref))
actor)
(error "can't dereferize ~S (actor not found)" ref))
scene)))
(defun pointerize (ref)
"Convert id-ref ref into a weak-pointer."
(declare (type id-ref ref))
(make-weak-pointer (dereferize ref)))
(defmacro referize-setf (place)
`(when (typep ,place 'weak-pointer)
(setf ,place (referize ,place))))
(defmacro pointerize-setf (place)
`(when (typep ,place 'id-ref)
(setf ,place (pointerize ,place))))
(defun replace-in-tree (tree atom-fun) (defun replace-in-tree (tree atom-fun)
(if (consp (car tree)) (if (consp (car tree))
(replace-in-tree (car tree) atom-fun) (replace-in-tree (car tree) atom-fun)