entitas-clj0.1.0-SNAPSHOTdependencies
| (this space intentionally left almost blank) | ||||||
(ns entitas-clj.collection (:require [entitas-clj.matcher :as m])) | |||||||
(defn init-with-matcher [matcher mname mkey] (atom {:mkey mkey :name mname :matcher matcher :entities {} :add-observers #{} :remove-observers {}})) | |||||||
(defn init-with-types [ctypes] (let [mname "entitas-clj.matcher/all-of-set" mkey (m/to-key mname ctypes)] (init-with-matcher #(entitas-clj.matcher/all-of-set ctypes %) mname mkey))) | |||||||
(defn entities [collection] (vals (:entities @collection))) | |||||||
(defn type-matcher [collection] (:matcher @collection)) | |||||||
(defn notify [observers event-type collection entity] (doseq [observer observers] (observer entity collection))) | |||||||
(defn do-add [c path entity] (if (nil? (get-in c path)) (assoc-in c path entity) c)) | |||||||
(defn add-entity [collection entity] (let [path [:entities (:creation-index @entity)]] (swap! collection do-add path entity) (notify (:add-observers @collection) :added collection entity) collection)) | |||||||
(defn exchange-entity [collection entity] (let [path [:entities (:creation-index @entity)]] (if (nil? (get-in @collection path)) (add-entity collection entity) (do (swap! collection assoc-in path entity) (notify (:remove-observers @collection) :removed collection entity) (notify (:add-observers @collection) :added collection entity) collection)))) | |||||||
(defn remove-entity [collection entity] (let [creation-index (:creation-index @entity) path [:entities creation-index]] (if (nil? (get-in @collection path)) collection (do (swap! collection update-in [:entities] dissoc creation-index) (notify (:remove-observers @collection) :removed collection entity) collection)))) | |||||||
(defn add-observer [collection observer event-type] (let [path (case event-type :added [:add-observers] :removed [:remove-observers])] (swap! collection update-in path conj observer))) | |||||||
(defn remove-observer [collection observer event-type] (let [path (case event-type :added [:add-observers] :removed [:remove-observers])] (swap! collection update-in path remove observer))) | |||||||
This file autogenerated from src/cljx/entitas_clj/collection.cljx | |||||||
(ns entitas-clj.component) | |||||||
(defn create ([type] (create type nil)) ([type data] {:type type :data data})) | |||||||
This file autogenerated from src/cljx/entitas_clj/component.cljx | |||||||
(ns entitas-clj.core (:require [entitas-clj.entity :as e] [entitas-clj.repository :as r])) | |||||||
(set! *warn-on-reflection* true) | |||||||
(defn add-component [repository entity component] (let [new-entity (e/add-component entity component)] (r/add-component repository (:type component) new-entity))) | |||||||
(defn exchange-component [repository entity component] (let [new-entity (e/exchange-component entity component)] (r/exchange-component repository (:type component) new-entity))) | |||||||
(defn remove-component [repository entity ctype] (let [new-entity (e/remove-component-of-type entity ctype)] (r/remove-component repository ctype new-entity))) | |||||||
This file autogenerated from src/cljx/entitas_clj/core.cljx | |||||||
(ns entitas-clj.entity (:require [clojure.set :refer [subset?]])) | |||||||
(defn create [id & comps] (let [components (into {} (map (fn [{:keys [type] :as comp}] [type comp]) comps)) ctypes (set (map :type comps))] (atom {:id id :creation-index nil :components components :ctypes ctypes}))) | |||||||
(defn has-component-of-type [entity ctype] (contains? (:ctypes @entity) ctype)) | |||||||
(defn has-components-of-types [entity ctypes] (subset? ctypes (:ctypes @entity))) | |||||||
(defn component-of-type [entity ctype] (get-in @entity [:components ctype])) | |||||||
(defn data-for-component [entity ctype] (:data (component-of-type entity ctype))) | |||||||
(defn contains-component [entity component] (not (nil? (component-of-type entity (:type component))))) | |||||||
(defn- do-add [entity component] (let [ctype (:type component)] (swap! entity #(-> % (update-in ,, [:ctypes] conj ctype) (assoc-in ,, [:components ctype] component)))) entity) | |||||||
(defn add-component [entity component] (if (not (has-component-of-type entity (:type component))) (do-add entity component) entity)) | |||||||
(defn exchange-component [entity component] (do-add entity component)) | |||||||
(defn remove-component-of-type [entity ctype] (when (has-component-of-type entity ctype) (swap! entity (fn [a] (update-in a [:ctypes] #(set (remove #{ctype} %))) (update-in a [:components] #(dissoc % ctype))))) entity) | |||||||
This file autogenerated from src/cljx/entitas_clj/entity.cljx | |||||||
(ns entitas-clj.example (:require [entitas-clj.core :as cr] [entitas-clj.component :as c] [entitas-clj.collection :as cl] [entitas-clj.entity :as e] [entitas-clj.repository :as r] [entitas-clj.system :as s] [lanterna.screen :as ls] [clojure.core.async :refer [chan go put! alts! <! >! timeout]])) | |||||||
(def timeout-value 50) | |||||||
(defn create-player [x y] (e/create :player (c/create :position {:x x :y y}) (c/create :player) (c/create :render {:char "X"}))) | |||||||
(defn create-enemy [x y] (e/create :enemy (c/create :position {:x x :y y}) (c/create :render {:char "E"}))) | |||||||
(defn handle-input [{:keys [x y] :as position} input] (let [[new-x new-y] (case input :left [(- x 1) y] :right [(inc x) y] :up [x (- y 1)] :down [x (inc y)] [x y])] (assoc position :x new-x :y new-y))) | |||||||
(defn update-position [entity input] (let [component (e/component-of-type entity :position)] (update-in component [:data] handle-input input))) | |||||||
(defn input->entities [screen] (loop [input (ls/get-key screen) acc []] (if (not (nil? input)) (let [comp (c/create :key-press {:input input}) new-acc (conj acc (e/add-component (e/create nil) comp))] (recur (ls/get-key screen) new-acc)) acc))) | |||||||
(defn collect-input [repository screen] (let [input-entities (input->entities screen)] (reduce (fn [acc entity] (r/add-entity acc entity)) repository input-entities))) | |||||||
(defn execute-enemy-move-system [repository] (let [[new-repository rc] (r/collection-for-types repository #{:enemy})] new-repository)) | |||||||
(defn get-player-entity [repository] (let [[r0 pc] (r/collection-for-types repository #{:player}) player (first (cl/entities pc))] [r0 player])) | |||||||
(defn execute-input-system [repository] (let [[r0 player] (get-player-entity repository) [new-repository rc] (r/collection-for-types r0 #{:key-press})] (reduce (fn [acc entity] (let [input (:data (e/component-of-type entity :key-press)) position-component (update-position player (:input input)) r1 (cr/exchange-component acc player position-component) r2 (r/remove-entity r1 entity)] r2)) new-repository (cl/entities rc)))) | |||||||
(defn execute-render-system [screen repository] (let [[new-repository rc] (r/collection-for-types repository #{:render})] (ls/clear screen) (doseq [entity (cl/entities rc)] (let [{:keys [x y]} (e/data-for-component entity :position) {:keys [char]} (e/data-for-component entity :render)] (ls/put-string screen x y char))) (ls/redraw screen) new-repository)) | |||||||
(defn initial-state [width height] (let [player (create-player 0 0) enemy (create-enemy 22 22) repository (-> (r/create) (r/add-entity ,, player) (r/add-entity ,, enemy)) input-system (s/create :input-system execute-input-system) enemy-system (s/create :enemy-system execute-enemy-move-system) screen (ls/get-screen :swing {:cols width :rows height}) render-system (s/create :render-system #(execute-render-system screen %)) systems [input-system enemy-system render-system]] (ls/start screen) {:systems systems :repository repository :screen screen})) | |||||||
(defn start [width height] (let [command-chan (chan)] (go (loop [state (initial-state width height) timer (timeout timeout-value)] (let [[v c] (alts! [timer command-chan])] (condp = c command-chan (when v (recur state timer)) timer (let [repository0 (collect-input (:repository state) (:screen state)) repository1 (s/execute (:systems state) repository0)] (recur (assoc state :repository repository1) (timeout timeout-value))))))))) | |||||||
(start 40 40) | |||||||
(ns entitas-clj.macros) | |||||||
(defmacro with-time [& body] (let [fname (first (flatten body))] `(let [start-time# (.getTime (js/Date.)) result# ~@body end-time# (.getTime (js/Date.))] (.log js/console ~fname "---" (- end-time# start-time#) "ms") result#))) | |||||||
(ns entitas-clj.matcher (:require [clojure.set :refer [subset? intersection]])) | |||||||
(defn all-matching? [a b] (subset? a b)) | |||||||
(defn any-matching? [a b] (not (= #{} (intersection a b)))) | |||||||
(defn equal? [a b] (= a b)) | |||||||
(defn all-of [ctypes-a & ctypes-b] (all-matching? ctypes-a (set ctypes-b))) | |||||||
(defn all-of-set [ctypes-a ctypes-b] (all-matching? ctypes-a ctypes-b)) | |||||||
(defn any-of [ctypes-a & ctypes-b] (any-matching? ctypes-a (set ctypes-b))) | |||||||
(defn any-of-set [ctypes-a ctypes-b] (any-matching? ctypes-a ctypes-b)) | |||||||
(defn just [type-a ctypes-b] (all-matching? #{type-a} ctypes-b)) | |||||||
(defn to-key [mtype ctypes] (let [safe-ctypes (if (coll? ctypes) ctypes [ctypes])] (apply str mtype safe-ctypes))) | |||||||
This file autogenerated from src/cljx/entitas_clj/matcher.cljx | |||||||
(ns entitas-clj.performance (:gen-class) (:require [entitas-clj.core :as cr] [entitas-clj.repository :as r] [entitas-clj.entity :as e] [entitas-clj.component :as cm] [entitas-clj.collection :as c] [entitas-clj.matcher :as m])) | |||||||
(def simple-component {:type :foo :a 1 :b 2}) (def another-simple-component {:type :bar :x 1 :y 2}) | |||||||
(defn entry-creation-bench [repository num-entities ctype1 ctype2] (reduce (fn [acc i] (let [entity (e/create :foo) new-acc (r/add-entity acc entity)] (condp = (mod i 25) 0 (cr/add-component new-acc entity (cm/create ctype1)) 1 (cr/add-component new-acc entity (cm/create ctype2)) new-acc))) repository (range num-entities))) | |||||||
(defn collection-creation-bench [repository ctype] (r/collection-for-types repository #{ctype})) | |||||||
(defn getting-all-entities-initially [collection] (let [result (atom nil)] (doseq [n (range 100)] (reset! result (c/entities collection))) @result)) | |||||||
(defn getting-all-entities-from-repository [repository ctype] (let [result (atom nil) mtype entitas-clj.matcher/all-of-set mname "entitas-clj.matcher/all-of-set" matcher-config {:mtype mtype :mname mname :ctypes #{ctype}}] (doseq [n (range 100)] (let [[new-repository entities] (r/entities-for-matcher repository matcher-config)] (reset! result entities))) @result)) | |||||||
(defn exchange-component-in-all-entities [repository entities] (let [[i result] (reduce (fn [[idx acc] entity] (let [r (cr/exchange-component acc entity (cm/create :foo))] [(inc idx) r])) [0 repository] entities)] (println "exhanged" i "components") result)) | |||||||
(defn destroy-all-entities [repository] (reduce (fn [acc entity] (r/remove-entity acc entity)) repository (r/all-entities repository))) | |||||||
(defmacro with-time [& body] (let [fname (first (flatten body))] `(let [start-time# (System/currentTimeMillis) result# ~@body] (println ~fname "---" (- (System/currentTimeMillis) start-time#) "ms") result#))) | |||||||
(defn run-test [entity-count] (let [ctype1 :foo ctype2 :bar r1 (with-time (entry-creation-bench (r/create) entity-count ctype1 ctype2)) [r2 c1] (with-time (collection-creation-bench r1 ctype1)) c2 (with-time (getting-all-entities-initially c1)) c3 (with-time (getting-all-entities-from-repository r2 ctype1)) r3 (with-time (exchange-component-in-all-entities r2 (vals c3))) r4 (with-time (destroy-all-entities r3))] nil)) | |||||||
(defn -main [& args] (dotimes [n 100] (run-test 1000000))) | |||||||
This file autogenerated from src/cljx/entitas_clj/performance.cljx | |||||||
(ns entitas-clj.repository (:require [entitas-clj.entity :as e] [entitas-clj.collection :as c] [entitas-clj.matcher :as m])) | |||||||
(defn create [] {:entities {} :collections {} :collections-for-type {} :current-index 0}) | |||||||
(defn all-entities [repository] (vals (:entities repository))) | |||||||
(defn contains-entity [repository entity] (not (nil? (get-in repository [:entities (:creation-index @entity)])))) | |||||||
(defn internal-collections-for-type [repository ctype] (or (get-in repository [:collections-for-type ctype]) #{})) | |||||||
(defn memoize-matcher [repository {:keys [mtype mname ctypes] :as matcher-config}] (let [matcher #(mtype ctypes %) mkey (m/to-key mname ctypes) mcoll (reduce (fn [acc entity] (if (matcher (:ctypes @entity)) (c/add-entity acc entity) acc)) (c/init-with-matcher matcher mname mkey) (all-entities repository)) r0 (update-in repository [:collections] assoc mkey mcoll) r1 (reduce (fn [acc ctype] (let [f (fnil (fn [colls] (conj colls mcoll)) #{})] (update-in acc [:collections-for-type ctype] f))) r0 ctypes)] [r1 mcoll])) | |||||||
(defn collection-for-matcher [repository {:keys [mtype mname ctypes] :as matcher-config}] (let [mkey (m/to-key mname ctypes) collection (get-in repository [:collections mkey])] (if (nil? collection) (memoize-matcher repository matcher-config) [repository collection]))) | |||||||
(defn entities-for-matcher [repository matcher-config] (let [[new-repository collection] (collection-for-matcher repository matcher-config)] [new-repository (c/entities collection)])) | |||||||
(defn collection-for-types [repository ctypes] (let [mtype entitas-clj.matcher/all-of-set mname "entitas-clj.matcher/all-of-set"] (collection-for-matcher repository {:mtype mtype :mname mname :ctypes ctypes}))) | |||||||
(defn add-component [repository ctype entity] (let [f (fn [collection] (if ((:matcher @collection) (:ctypes @entity)) (c/add-entity collection entity) collection)) cft (set (map f (internal-collections-for-type repository ctype)))] (if (empty? cft) repository (assoc-in repository [:collections-for-type ctype] cft)))) | |||||||
(defn exchange-component [repository ctype entity] (let [f (fn [collection] (if ((:matcher @collection) (:ctypes @entity)) (c/exchange-entity collection entity) collection)) cft (set (map f (internal-collections-for-type repository ctype)))] (if (empty? cft) repository (assoc-in repository [:collections-for-type ctype] cft)))) | |||||||
(defn remove-component [repository ctype entity] (let [ctypes (:ctypes @entity) f (fn [collection] (if ((:matcher @collection) ctypes) (c/remove-entity collection entity) collection)) cft (set (map f (internal-collections-for-type repository ctype)))] (if (empty? cft) repository (assoc-in repository [:collections-for-type ctype] cft)))) | |||||||
(defn remove-entity [repository entity] (let [ctypes (:ctypes @entity) creation-index (:creation-index @entity) r1 (reduce (fn [acc ctype] (remove-component acc ctype entity)) repository ctypes)] (update-in r1 [:entities] dissoc creation-index))) | |||||||
(defn add-entity [{:keys [current-index] :as repository} entity] (swap! entity assoc :creation-index current-index) (let [components (vals (:components @entity)) r0 (-> repository (assoc-in ,, [:entities current-index] entity) (update-in ,, [:current-index] inc))] (reduce (fn [acc component] (add-component acc (:type component) entity)) r0 components))) | |||||||
This file autogenerated from src/cljx/entitas_clj/repository.cljx | |||||||
(ns entitas-clj.system (:refer-clojure :exclude [remove]) (:require [clojure.core.async :refer [chan go sliding-buffer put! alts!]])) | |||||||
system | |||||||
(defn create [type execute-fn] {:type type :active true :execute-fn execute-fn :activate-fn nil :deactivate-fn nil}) | |||||||
systems | |||||||
(defn create-systems [] []) | |||||||
(defn add [systems system] (conj systems system)) | |||||||
(defn contains [systems system] (some #{system} systems)) | |||||||
(defn remove [systems system] (vec (clojure.core/remove #{system} systems))) | |||||||
(defn execute [systems repository] (reduce (fn [acc system] ((:execute-fn system) acc)) repository systems)) | |||||||
(defn activate [systems] (vec (map (fn [system] ((:activate-fn system) system) (assoc system :active true)) systems))) | |||||||
(defn deactivate [systems] (vec (map (fn [system] ((:deactivate-fn system) system) (assoc system :active false)) systems))) | |||||||
(defn remove-all [systems] []) | |||||||
This file autogenerated from src/cljx/entitas_clj/system.cljx | |||||||