454 lines
13 KiB
Scheme
454 lines
13 KiB
Scheme
(module (engine core) ()
|
|
(import scheme
|
|
(chicken base)
|
|
(chicken module)
|
|
(chicken sort)
|
|
(engine guards)
|
|
raylib
|
|
(srfi 1)
|
|
(srfi 4)
|
|
(srfi 69)
|
|
(srfi 99)
|
|
(srfi 113)
|
|
(srfi 128))
|
|
|
|
;; We export the world, component sets, systems, and event buses so that the core can be extended to
|
|
;; include serialization functions across all.
|
|
;; NOTE: direct read/write to these is undefined, and will cause problems.
|
|
(export world component-sets systems event-buses render-queues render-priority)
|
|
|
|
;; The world hash table
|
|
(define world (make-hash-table))
|
|
;; The component-sets hash table
|
|
(define component-sets (make-hash-table))
|
|
|
|
;; Insert an entity into component-sets, either creating
|
|
;; a new set or updating an existing one for the component
|
|
;; type provided.
|
|
(define (classify-entity-by id component)
|
|
(let ((component-type (rtd-name (record-rtd component))))
|
|
(if (hash-table-exists? component-sets component-type)
|
|
(let ((component-set (hash-table-ref component-sets component-type)))
|
|
;; TODO: this could be nicer with set-adjoin! but I'm not sure if hash-table-ref is locative
|
|
(hash-table-set! component-sets
|
|
component-type
|
|
(set-adjoin component-set
|
|
id)))
|
|
(hash-table-set! component-sets
|
|
component-type
|
|
(set (make-eq-comparator) id)))))
|
|
|
|
;; Create an entity in the world immediately,
|
|
;; and add it to the requisite component-sets
|
|
(define (create-entity-now id components)
|
|
(hash-table-set! world id components)
|
|
(for-each
|
|
(lambda (component)
|
|
(classify-entity-by id component))
|
|
components))
|
|
|
|
;; Remove a single matching item from a set
|
|
(define (remove-from-set! set element)
|
|
(set-search! set element
|
|
(lambda (insert ignore)
|
|
(ignore #f))
|
|
(lambda (old update remove)
|
|
(remove old))))
|
|
|
|
;; Remove an entity from the world immediately,
|
|
;; as well as from all component sets.
|
|
(define (remove-entity-now id)
|
|
(hash-table-delete! world id)
|
|
(for-each
|
|
(lambda (set)
|
|
(remove-from-set! set id))
|
|
(hash-table-values component-sets)))
|
|
|
|
;; Queues for entity creation and deletion
|
|
(define add-entity-queue '())
|
|
(define del-entity-queue '())
|
|
|
|
;; Add an entity to the incoming queue
|
|
;; TODO: append! doesn't work here and IDK why
|
|
;; TODO: this could be done with cons anyway because we don't care about the order
|
|
(define (queue-add-entity id components-lst)
|
|
(set! add-entity-queue
|
|
(append add-entity-queue (list (cons id components-lst)))))
|
|
|
|
;; Add an entity to the deletion queue
|
|
(define (queue-del-entity id)
|
|
(set! del-entity-queue
|
|
(append del-entity-queue (list id))))
|
|
|
|
;; Resolve all new entity creations
|
|
(define (add-queued-entities)
|
|
(for-each
|
|
(lambda (entity)
|
|
(let ((id (car entity))
|
|
(components (cdr entity)))
|
|
(create-entity-now id components)))
|
|
add-entity-queue)
|
|
(set! add-entity-queue '()))
|
|
|
|
;; Resolve all queued entity deletions
|
|
(define (remove-queued-entities)
|
|
(for-each
|
|
(lambda (id)
|
|
(remove-entity-now id))
|
|
del-entity-queue)
|
|
(set! del-entity-queue '()))
|
|
|
|
;; Resolve all queued entity changes
|
|
(define (resolve-entity-queue)
|
|
(remove-queued-entities)
|
|
(add-queued-entities))
|
|
|
|
;; Entity creation/removal interface
|
|
(export create-named-entity create-entity remove-entity
|
|
get-entity clear-world)
|
|
|
|
(define (get-entity id)
|
|
(assert (symbol? id))
|
|
(hash-table-ref world id))
|
|
|
|
;; Create an entity in the world and return it's ID
|
|
;; TODO: consider whether we want to use a hash-table for the entity list. I'm not sure yet how punishing O(n) lookup will be here, so it might be sometihng to look at once we start handling entity execution
|
|
(define (create-named-entity id . components)
|
|
(assert (symbol? id))
|
|
(assert (every record? components))
|
|
(queue-add-entity id components)
|
|
id)
|
|
|
|
;; Shortcut for anonymous instancing
|
|
(define (create-entity . components)
|
|
(apply create-named-entity (gensym) components))
|
|
|
|
;; Remove an entity from the world
|
|
(define (remove-entity id)
|
|
(assert (symbol? id))
|
|
(queue-del-entity id)
|
|
id)
|
|
|
|
;; Clear the entire world
|
|
(define (clear-world)
|
|
(for-each
|
|
remove-entity
|
|
(hash-table-keys world)))
|
|
|
|
;; System record interface
|
|
(export make-system system? system-name
|
|
system-priority set-system-priority! system-criteria
|
|
set-system-criteria! system-process set-system-process!)
|
|
|
|
;; The system record
|
|
(define-record-type <system>
|
|
(int:make-system name priority mode criteria process)
|
|
system?
|
|
(name system-name)
|
|
(priority system-priority int:set-system-priority!)
|
|
(mode system-mode)
|
|
(criteria system-criteria int:set-system-criteria!)
|
|
(process system-process int:set-system-process!))
|
|
|
|
;; Type-checked system constructor wrapper
|
|
(define (make-system name priority mode criteria process)
|
|
(assert (symbol? name))
|
|
(assert (integer? priority))
|
|
(assert (member mode '(enity batch)))
|
|
(assert (every symbol? criteria))
|
|
(assert (procedure? process))
|
|
(int:make-system name priority mode criteria process))
|
|
|
|
;; Type-checked system priority mutator
|
|
(define (set-system-priority! system priority)
|
|
(assert (system? system))
|
|
(assert (integer? priority))
|
|
(int:set-system-priority! system priority))
|
|
|
|
;; Type-checked system criteria mutator
|
|
(define (set-system-criteria! system criteria)
|
|
(assert (system? system))
|
|
(assert (every symbol? criteria))
|
|
(int:set-system-criteria! system criteria))
|
|
|
|
;; Type-checked system process mutator
|
|
(define (set-system-process! system process)
|
|
(assert (system? system))
|
|
(assert (procedure? process))
|
|
(int:set-system-process! system process))
|
|
|
|
;; The systems list
|
|
(define systems '())
|
|
|
|
;; Immediately add a new system
|
|
(define (add-system-now system)
|
|
(set! systems
|
|
(append systems (list system))))
|
|
|
|
;; Immediately remove a new system
|
|
(define (remove-system-now name)
|
|
(set! systems
|
|
(delete name systems
|
|
(lambda (n el)
|
|
(eqv? n (system-name el))))))
|
|
|
|
;; Sort the systems list by priority
|
|
(define (sort-systems)
|
|
(sort! systems
|
|
(lambda (x y)
|
|
(< (system-priority x)
|
|
(system-priority y)))))
|
|
|
|
;; System addition/deletion queues
|
|
(define add-system-queue '())
|
|
(define del-system-queue '())
|
|
|
|
;; Add a system to the incoming queue
|
|
(define (queue-add-system system)
|
|
(set! add-system-queue
|
|
(append add-system-queue (list system))))
|
|
|
|
;; Add a system to the deletion queue
|
|
(define (queue-del-system name)
|
|
(set! del-system-queue
|
|
(append del-system-queue (list name))))
|
|
|
|
;; Resolve all new system creations
|
|
(define (add-queued-systems)
|
|
(for-each
|
|
(lambda (system)
|
|
(add-system-now system))
|
|
add-system-queue)
|
|
(set! add-system-queue '()))
|
|
|
|
;; Resolve all queued system deletions
|
|
(define (remove-queued-systems)
|
|
(for-each
|
|
(lambda (system-name)
|
|
(remove-system-now system-name))
|
|
del-system-queue)
|
|
(set! del-system-queue '()))
|
|
|
|
;; Resolve all queued system changes
|
|
(define (resolve-system-queue)
|
|
(remove-queued-systems)
|
|
(add-queued-systems)
|
|
(sort-systems))
|
|
|
|
;; System creation/removal interface
|
|
(export add-system remove-system clear-systems)
|
|
|
|
;; Add a system to the state processing and return its name
|
|
(define (add-system system)
|
|
(assert (system? system))
|
|
(queue-add-system system)
|
|
(system-name system))
|
|
|
|
;; Remove a system from process
|
|
(define (remove-system name)
|
|
(assert (symbol? name))
|
|
(queue-del-system name)
|
|
name)
|
|
|
|
(define (clear-systems)
|
|
(for-each
|
|
remove-system
|
|
(map system-name systems)))
|
|
|
|
;; Execute a single system
|
|
;; TODO: Implement
|
|
(define (execute-system system)
|
|
(assert (system? system))
|
|
#t)
|
|
|
|
(define (execute-systems)
|
|
(for-each
|
|
execute-system
|
|
systems))
|
|
|
|
;; Event buses hash table
|
|
(define event-buses (make-hash-table))
|
|
|
|
;; Event bus interface
|
|
(export register-event-bus remove-event-bus fetch-event-bus
|
|
push-event peek-event pop-event)
|
|
|
|
;; Register a new event bus
|
|
(define (register-event-bus name)
|
|
(assert (symbol? name))
|
|
(if (hash-table-exists? event-buses name)
|
|
#f
|
|
(begin
|
|
(hash-table-set! event-buses name (make-hash-table))
|
|
name)))
|
|
|
|
(register-event-bus 'input)
|
|
|
|
;; Remove an event bus
|
|
(define (remove-event-bus name)
|
|
(assert (symbol? name))
|
|
(if (hash-table-exists? event-buses name)
|
|
(begin
|
|
(hash-table-delete! event-buses name)
|
|
name)
|
|
#f))
|
|
|
|
;; Fetch an event bus by name, or #f if it doesn't exist
|
|
(define (fetch-event-bus name)
|
|
(assert (symbol? name))
|
|
(if (hash-table-exists? event-buses name)
|
|
(hash-table-ref event-buses name)
|
|
#f))
|
|
|
|
;; Push an event to the specified bus, return #f on failure (if the bus doesn't exist
|
|
(define (push-event bus action event)
|
|
(assert (symbol? bus))
|
|
(assert (symbol? action))
|
|
(assert (record? event))
|
|
(let ((event-bus (fetch-event-bus bus)))
|
|
(if event-bus
|
|
(begin
|
|
(hash-table-set! event-bus action event)
|
|
#t)
|
|
#f)))
|
|
|
|
;; Retrieve an event from the event bus, if it exists. Return false if it doesn't
|
|
(define (peek-event bus action)
|
|
(assert (symbol? bus))
|
|
(assert (symbol? action))
|
|
(let ((event-bus (fetch-event-bus bus)))
|
|
(if (and event-bus
|
|
(hash-table-exists? event-bus action))
|
|
(hash-table-ref event-bus action)
|
|
#f)))
|
|
|
|
;; Retrieve an event from the event bus, if it exists, then delete it. Return false if it doesn't exist.
|
|
(define (pop-event bus action)
|
|
(assert (symbol? bus))
|
|
(assert (symbol? action))
|
|
(let ((event-bus (fetch-event-bus bus)))
|
|
(if (and event-bus
|
|
(hash-table-exists? event-bus action))
|
|
(let ((event (hash-table-ref event-bus action)))
|
|
(hash-table-delete! event-bus action)
|
|
event)
|
|
#f)))
|
|
|
|
;; Render queue exports
|
|
(export register-render-queue push-render-object evaluate-render-queue)
|
|
|
|
;; Render queues
|
|
(define render-queues (make-hash-table))
|
|
(define render-priority '())
|
|
|
|
;; Register a new render queue
|
|
(define (register-render-queue name drawing)
|
|
(assert (symbol? name))
|
|
(assert (and (symbol? drawing) (member drawing '(screen 2d 3d))))
|
|
(if (hash-table-exists? render-queues name)
|
|
#f
|
|
(begin
|
|
(hash-table-set! render-queues name '())
|
|
(set! render-priority (cons (cons name drawing) render-priority))
|
|
name)))
|
|
|
|
;; Default render queues
|
|
(register-render-queue '3d '3d)
|
|
(register-render-queue '2d '2d)
|
|
(register-render-queue 'screen 'screen)
|
|
|
|
;; TODO: camera selection needs a better solution, ideally treating the camera
|
|
;; as an entity.
|
|
(define *active-camera-2d* (make-parameter '()))
|
|
(define *active-camera-3d* (make-parameter '()))
|
|
|
|
;; Push a render call to the queue
|
|
(define (push-render-object queue-name layer thunk)
|
|
(assert (symbol? queue-name))
|
|
(assert (integer? layer))
|
|
(assert (procedure? thunk))
|
|
(hash-table-set! render-queues queue-name
|
|
(cons (cons layer thunk) (hash-table-ref render-queues queue-name))))
|
|
|
|
;; Evaluate a single render queue with a specific mode.
|
|
;; Render objects are sorted by their layer
|
|
(define (evaluate-render-queue queue-name mode)
|
|
(assert (symbol? queue-name))
|
|
(assert (symbol? mode) (member mode '(screen 2d 3d)))
|
|
(let ((queue (hash-table-ref render-queues queue-name)))
|
|
(when (or (eq? mode 'screen)
|
|
(and (eq? mode '2d)
|
|
(not (null? (*active-camera-2d*))))
|
|
(and (eq? mode '3d)
|
|
(not (null? (*active-camera-3d*)))))
|
|
(when (eq? mode '2d)
|
|
(begin-mode-2d (*active-camera-2d*)))
|
|
(when (eq? mode '3d)
|
|
(begin-mode-3d (*active-camera-3d*)))
|
|
(for-each
|
|
(lambda (render-obj) (cdr render-obj))
|
|
(sort queue
|
|
(lambda (render-obj-a render-obj-b)
|
|
(< (car render-obj-a)
|
|
(car render-obj-b)))))
|
|
(when (eq? mode '2d)
|
|
(end-mode-2d))
|
|
(when (eq? mode '3d)
|
|
(end-mode-3d)))
|
|
(hash-table-set! render-queues queue-name '())))
|
|
|
|
;; Frame generation and game loop
|
|
(export resolve-queues next-frame *clear-color* perform-render)
|
|
|
|
;; Resolve the entity and system queues. This is exported which allows breaking iteration
|
|
(define (resolve-queues)
|
|
(resolve-entity-queue)
|
|
(resolve-system-queue))
|
|
|
|
;; Clear color
|
|
(define *clear-color* (guarded-parameter RAYWHITE u8vector?))
|
|
|
|
;; Render all queues
|
|
(define (perform-render)
|
|
(begin-drawing)
|
|
(clear-background (*clear-color*))
|
|
(for-each
|
|
(lambda (queue-spec)
|
|
(evaluate-render-queue (car queue-spec)
|
|
(cdr queue-spec)))
|
|
render-priority)
|
|
(end-drawing))
|
|
|
|
;; Generate the next frame, for use in the main game loop
|
|
(define (next-frame)
|
|
(resolve-queues)
|
|
(perform-render))
|
|
|
|
;; Window creation
|
|
(export *window-size* *window-title* create-window)
|
|
|
|
;; Window size to use
|
|
(define *window-size* (guarded-parameter '(768 . 576)
|
|
(lambda (x)
|
|
(and (pair? x)
|
|
(integer? (car x))
|
|
(integer? (cdr x))))))
|
|
|
|
;; Window title
|
|
(define *window-title* (guarded-parameter "imugi"
|
|
string?))
|
|
|
|
;; Desired FPS count
|
|
(define *target-fps* (guarded-parameter 60
|
|
integer?))
|
|
|
|
;; Make a window with the above parameters and default processing and predicate
|
|
(define (create-window #!key (process next-frame) (close-predicate window-should-close?))
|
|
(init-window (car (*window-size*)) (cdr (*window-size*)) (*window-title*))
|
|
(set-target-fps (*target-fps*))
|
|
(let loop ()
|
|
(process)
|
|
(unless (close-predicate)
|
|
(loop)))
|
|
(close-window))
|
|
)
|