-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Global singleton state #1
Comments
Hmnnn. I totally hear what you are saying! I see you are passing around I have actually written Clojure code like that before (and prefer it), but shied away from it here, because it didn't feel as 'library-esque' to force essentially be passing around global state. I'm going to let this roll around in my head for a bit, but I'm leaning in your direction at the moment. Sure it's an API breakage, but this library is v0, so that's okay 😄 My initial thoughts would be to have something akin to (writing this off the top of my head): (ns brute.entity)
(defn create-system
"Creates an entity system state"
[]
{:all-entities (ref #{})
:entity-components (ref {})
:entity-component-types (ref {})}) Which basically mimics my current global singleton state, but in a manner than can be passed around. I like having the multiple refs, as it allows for fast lookup and discovery. From there, things like (defn create-entity!
"Creates an entity and stores it"
[system]
(let [entity (java.util.UUID/randomUUID)]
(dosync
(alter (:all-entities system) conj entity)
(alter (:entity-component-types system) assoc entity #{}))
entity)) ...and so on through the API. Thoughts? Btw - thanks for the feedback. As I said, this is my first Clojure library, so it's been a great learning experience. |
No worries. I have some other code that illustrates the syntactic limitations of this approach, and involving refs at all turns out to be uncalled for thanks to the Basically what (def player
{:name "Jerry"
:location {:x 5 :y 3}
:inventory [{:type :sock :attrs #{}}]
}) and "decomposes" it into member single layer maps and vectors. So the decompose of this datastructure would be a pair (let [uuid_0 (uuid) uuid_1 (uuid) uuid_2 (uuid)]
{uuid_0 {:type :socks :attrs #{}}
uuid_1 {:x 5 :y 3}
uuid_2 {:name "Jerry" :location uuid_1 :inventory [uuid_0]}
}) What this lets you do is view your data two ways: either as a bunch of UUID's components in a structure, or thanks to the I will urge you to look away from atoms and refs. Except in cases where there is a clear need to share state between multiple threads, refs and atoms are almost universally the wrong tool for the job. For instance, with this map CES representation, realize that I can chain updaters just as happily by performing |
I was thinking just that right as soon as I hit submit on my last comment. You really just want to be passing in a immutable data structure into a function and then returning a new data structure which has the change in it. So the flow becomes something more like: (defn create-ball
[system]
(let [entity (e/create-entity) ; just returns a UUID, doesn't do anything else with it
system (e/add-entity system entity) ; returns the system with the entity attached
system (e/add-component system (Ball.)] ; returns the system with the instance of the Ball component attached to the entity
system) ; spit it back out the other side When actually writing real code, you wouldn't need all the let statements, but I'm just being really explicit so I can write and comment it all out (easy to make much cleaner with threading macros) Thanks for taking the time to explain, that's given me a great deal to think about. It's quite likely I'll rewrite this whole thing now 🤘 |
Welcome to clojure, the land where we weren't joking about that whole immutability thing 👊 |
As you say, threading macros make this look pretty nice:
|
Fixed in |
In reading this library, one thing stuck out to me like a sore thumb: every single facet of your CES is stored inside a set of globally shared atoms. This means that it's impossible to do simulations in your CES, it's impossible to have more than one CES at once... and to boot the global singleton pattern is a code smell in Clojure.
this is the CES that I built previously. The great advantage of modeling a CES this way, is that you can drop down to using a single shared ref if you have to, and when you don't, you can extract the value, update it functionally, and play with it as you see fit.
The text was updated successfully, but these errors were encountered: