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 | |||||||