#+title: Imugi Imugi is a framework for building games (primarily tactics games) with Chicken Scheme and raylib. The end goal of this framework is to create a game, also called Imugi, and release to show the capability here. * Architecture Imugi is an ECS engine. ** Entities Entities are game objects stored in an SRFI-69 hash table. A hash table is used for O(1) lookup. Entity keys within the hash table are symbols, either set intentionally or automatically generated via gensym. Entities are identified by their key, and are each a list of components. Entities are queued for addition and removal to/from the hash table ~world~. Entity removal and addition is performed at the beginning of each frame, with removal occuring first. There exists a hash map of SRFI-113 sets, ~component-sets~. When an entity is added to ~world~, every component of the entity is checked and the name of each component record is retrieved. The symbol representing the entity in ~world~ is then added to each set in the hash map associated with a component record type that the entity owns. This way, systems can query entities by the combination of components, using union operations. When an entity is removed from ~world~, it is also removed from every component set. ** Components Components are SRFI-99 records which store data associated for a particular entity. ** Systems Systems are functions which modify state and draw to the screen. Systems are stored in the ~systems~ list as a record containing a name (symbol), a priority (integer), entity criteria (list of symbols), and the function itself. Each system specifies the components an entity (the entity criteria) must have for it to be processed by that system. The entities are then selected for the system from the ~component-sets~. Each system is applied to all entities which match its entity criteria. Systems have a priority which defaults to 0. Lower priority systems will be executed before higher priority systems. Systems within the same priority execute in order of addition to the ~systems~ list. The ~systems~ list is sorted on each addition and removal of a system. Like entities, the addition of systems is queued for the beginning of each frame. This prevents the system list from being sorted multiple times per frame. Systems can access resources via free variables or by being formed as closures. Systems only ever take an entity (a component list) as input. ** Event Buses An event bus is a hash table of symbols and records. They allow for delegating changes sent from other objects. The event record can contain any arbitrary data. The symbol the record is associated by is unique within the association list. Event records stay in the bus until removed by another system. Systems can push and pull events to and from the bus. Systems can choose to either pop the event off the bus (removing it from further processing) or just peek at the event. The symbol an event is associated by is essentially an "action". The event bus can have multiple events of the same type (for example, multiple different key presses can occur in a frame). Only one event of each name (for example, only 1 event can be called "jump", which might correspond to a space key press) is allowed in the bus at any one time. Custom event buses can be created and stored in the ~event-buses~ hash table. Systems can query a particular event bus within that list for a particular event. ** Rendering Imugi maintains 3 render queues, with one each for screen, 2D and 3D drawing. New queues can be added by adding a new list to the ~render-queues~ hash table. Systems can push render objects to any of these queues, which are simply pairs of integers and a lambda thunk. The initial integer denotes a layer, while the lambda thunk contains the drawing function to evaluate. When drawing the next frame, the system iterates and executes every drawing thunk in order from the smallest to the largest layer. The 3D queue is executed first, followed by the 2D queue, followed by the screen queue. The order of execution can be changed by modifying the ~render-priority~ list. The render-priority list contains pairs where the first symbol in the pair is a reference to the render queue in the ~render-queues~ hash table, and the second symbol is either ~screen~, ~2d~, or ~3d~. The order of items in this list informs Imugi of the order in which to execute the queues, and the second symbol of each pair tells Imugi what draw mode to use for each thunk. For 2D and 3D drawing, systems can set the currently active camera object on the parameters ~*active-camera-2d*~ and ~*active-camera-3d*~. If a 3D or 2D render is queued but no corresponding camera for the mode is set, the render will not occur but the render queue will still be emptied. ** Resources When a resource, such as a font or texture, is loaded from the filesystem, the pointer is stored in an SRFI-69 hash table ~resources~ keyed by the path. When records are first loaded, a finalizer is set for them ensuring they are unloaded when collected by the GC. If the game attempts to load a resource which has already been loaded, it will use the pointer in the ~resources~ hash table instead. * Folder Structure & Module Documentation The ~engine~ folder contains core engine code, organised across the following modules: The ~games~ folder contains various sample games made with Imugi. ** ~(engine core)~ ~(engine core)~ contains the fundamental engine functions. Entire games can be made using just the core module, and all other modules simply extend this module. *** Game State ~world~ is an SRFI-69 hash table, which contains all entity component lists keyed by their symbol name. ~component-sets~ is an SRFI-69 hash table, which contains an SRFI-113 set for every component type in the ~world~. Each set contains a symbol referencing a ~world~ entity that has a component of that type. ~systems~ is a list, which contains all lists to be executed on each frame. ~event-buses~ is an SRFI-69 hash table, which contains hash tables keyed by the event bus name. Each hash table in ~event-buses~ keeps track of individual event records keyed by action names. ~render-queues~ is an SRFI-69 hash table, which contains lists keyed by the render queue name. Each item in these lists is a pair, where the first element is the layer of the drawing and the second is a thunk that gets called during the render. ~render-priority~ is an association list, where the first element of each pair is a render queue name from the ~render-queues~ hash table, and the second element is the drawing mode (a symbol which is either ~screen~, ~2d~, or ~3d~). The order of elements in the ~render-priority~ association list determines the order in which the render-queues are evaluated, with the first queue name in the list being the first queue to evaluate. *** Entities #+begin_src scheme (create-named-entity id . components) (create-entity . components) (remove-entity id) (get-entity id) (clear-world) #+end_src Functions for creating, removing, and fetching entities. ~id~ must be a symbol, and each parameter passed in as ~components~ must be an SRFI-99 record. When entities are created or removed, the creation/deletion is added to an internal queue. Entity creation and removal to/from the ~world~ state from the queue is performed at the top of each frame, or when ~(resolve-queues)~ is executed. *** Systems #+begin_src scheme (make-system name priority criteria process) (system? record) (system-name system) (system-priority system) (set-system-priority! system priority) (system-mode system) (system-criteria system) (set-system-criteria! system criteria) (system-process system) (set-system-process! system process) #+end_src Functions for creating systems, which are SRFI-99 records. ~name~ must be a symbol, ~priority~ must be an integer, ~mode~ must be either ~entity~ or ~batch~, ~criteria~ must be a list of symbols, and ~process~ must be a single argument procedure (where the single argument is expected to be either a single entity matching all given criteria when the mode is ~entity~, or a list of entities matching the given criteria when the mode is ~batch~). #+begin_src scheme (add-system system) (remove-system name) (clear-systems) #+end_src Functions for adding and removing systems from process. ~system~ must be an SRFI-99 record, and ~name~ must be a symbol (which is used to identify a ~system~ record in the ~systems~ list). When systems are created or removed, the creation/deletion is added to an internal queue. System creation and removal to/from the ~systems~ list from the queue is performed at the top of each frame, or when ~(resolve-queues)~ is executed. *** Event Buses #+begin_src scheme (register-event-bus name) (remove-event-bus name) (fetch-event-bus name) #+end_src Functions for creating, removing, and retrieving event buses. ~name~ must be a symbol referencing the bus name in the ~event-buses~ hash table. Event buses are added and remove immediately, without queuing. #+begin_src scheme (push-event bus action event) (peek-event bus action) (pop-event bus action) #+end_src Functions for creating and fetching events. ~bus~ must be a symbol referencing a bus name, ~action~ must be a symbol referencing the name of the action, and ~event~ must be a record. ~peek-event~ returns the event record but keeps the event in the event bus. ~pop-event~ returns the event record and removes it from the event bus. *** Frame Generation, Render Queues, and Game Loop #+begin_src scheme (register-render-queue queue-name drawing) #+end_src Create a new render queue in ~render-queues~, where ~queue-name~ is the queue name symbol and ~drawing~ is the drawing mode (either ~screen~, ~2d~, or ~3d~). Render queues created this way are automatically placed at the front of the ~render-priority~ assocation list. #+begin_src scheme (push-render-object queue-name layer thunk) #+end_src Adds a thunk to the given render queue name at the given layer. ~queue-name~ must be a symbol, ~layer~ must be an integer, and ~thunk~ must be a zero-argument procedure. #+begin_src scheme *active-camera-2d* *active-camera-3d* #+end_src Parameters which describe the currently active camera for 2D and 3D drawing. #+begin_src scheme *clear-color* #+end_src ~*clear-color*~ is a parameter which expects a u8vector corresponding to a Raylib color. #+begin_src scheme (evaluate-render-queue queue-name mode) (perform-render) #+end_src Functions for evaluating the render queues. ~evaluate-render-queue~ evaluates a single queue where ~queue-name~ is a symbol corresponding to a queue in the ~render-queues~, and ~mode~ is a drawing mode symbol (either ~screen~, ~2d~, or ~3d~). The given queue is cleared after evaluation, and each thunk in the render queue is evaluated with the appropriate mode, using the ~*active-camera-2d*~ and ~*active-camera-3d*~ for the ~2d~ and ~3d~ drawing modes. ~perform-render~ clears the screen to the set ~*clear-color*~, then iterates the ~render-priority~ association list and calls ~evaluate-render-queue~ on each queue in order with the appropriate mode. #+begin_src scheme (resolve-queues) #+end_src ~(resolve-queues)~ causes all queues to immediately resolve pending actions to the true game state. #+begin_src scheme (next-frame) #+end_src ~(next-frame)~ is a frame generation function. When called, it resolves all queues, then executes all systems, then finally performs the render of the next frame. *** Window Functions #+begin_src *window-size* *window-title* *target-fps* #+end_src ~*window-size*~ is a parameter that expects a cons pair of two integers, where the first integer is the window width, and the second is the window height. ~*window-title*~ is a parameter that expects a string, which is used for the window title. ~*target-fps*~ is a parameter that expects an integer, which specifies the desired frames per second to run at. #+begin_src scheme (create-window process: (next-frame) close-predicate: (window-should-close?)) #+end_src ~create-window~ creates a window using the window parameters described above, sets the target frames per second, then enters a loop which runs the ~process:~ function (~next-frame~ by default) on each frame, unless ~close-predicate:~ (Raylib's ~window-should-close~ function by default) returns true, in which case the window is closed. * Dependencies The following Chicken dependencies are required: - raylib - SRFI-99 - SRFI-113 - SRFI-69 - csm - SRFI-78 * Running From the ~build~ directory, run ~csm -program test.engine .. && ./test.engine~ to run engine tests.