Hey I’m developing a game in Clojure. Looking forward to exchanging ideas about best practices.
What are you using on FE? Re-frame etc. or do you paint in canvas?
@jakub.zika-extern I normally use re-frame for my projects. For the game instead I use html divs and css animations. I would switch if I find a good what to handle the animations
@merklefabian I am playing with Quil currently (wrapper over processing(js)), it is nice. You can create simple 2d/3d games with it. http://quil.info/api You can also provide atom to quil so it can be easily connected to RUM (and reagent and reframe - but i did not try it yet), so you can control your canvas by custom html elements… + I would recommend tips taken from: https://github.com/alexkehayias/chocolatier Tips Here are some tips for optimizing performance of game loops: • Use the Chrome dev tools to do a CPU profile and trace where time is being spent • Don’t use `partial` or `apply` as they are slow • Always specify each arrity of a function instead of using `(fn [& args] ...)` • Don’t use `multimethod`, use `cond` or `condp` and manually dispatch • Use `array` when you need to quickly append items to a collection (mutable) • Use `loop` instead of `for` or `into` with transients or arrays as the accumulator • Avoid boxing and unboxing i.e multiple maps/fors over a collection, use transducers, reducers or loops • Don’t make multiple calls to get the same data, put it in a `let` • Avoid heavily nested closures as the lookup tree becomes very long and slow • Favor eager operations over lazy operations i.e `reduce` instead of `for` • Don’t use `concat` or `mapcat` as they can be slow and generate lots of garbage • Don’t use `last` as it will need to traverse the whole sequence, use `nth` instead if you know how many elements are in the collection • Don’t use hashmaps as functions `({:a 1} :a)`, instead use `get` or keywords as functions • Always return the same type from a function (V8 can then optimize it)
@jakub.zika-extern Thanks for the extensive tips. I saw chocolatier before but had the impression that it was discontinued. Quil looks interesting. Why do you say Don’t use
multimethod` `
Chocolatier is discontinued, yeah.
Multimethod can get slower than using condp
.
Your code can get ugly once you will start optimizing JS / Java in Clojure - or you can keep your game simple and it should be fast enough 🙂.
Slower of call time or initialization? I’m using multimethod a lot to separate out the logic of different cards. Didn’t notice any huge speed difference.
I remember that i had got some better call time results with condp but dont take it as granted. It is long time ago…
What are you using Quil for?
I did some fun stuff like walking in 3d environment with wasd and jumping etc. But nothing is finished yet. I am more interested in creating the engine and the toolset than the game itself to be honest.
@jakub.zika-extern I guess that means one day I will be using your engine then, ha
What are you looking to use?
@merklefabian What libraries or tools are you using?
Thanks. Not planing to query that data, just need to be able to persist it through code deploys and then load it into memory again.
@oconn could you show me how you manage the web sockets and allocate a connection to a specific user/match? I’m reworking the networking code because I’m introducing authentication, login ect. Glad to have found someone who uses sockets as well ~
This is just the way we’re doing it - hope some of this helps; 1. There is an unauthenticated ws connection that gets upgraded upon authentication 2. On an upgrade connection event, a row is created in our DB that maintains the connection details for that user 3. When a game is created, references to all the users (who in turn have references to their current connection identifiers) are stored in the DB for that game 4. When an event occurs within that game that needs to be broadcasted to all the participants, it has access to all the connection-ids.
When it comes to managing the connection pools, we don’t do that. We rely on a 3rd party service for that.
@oconn Thanks for your answer. Would you share the code how you do 1. with me, please?
@merklefabian No problem. That workflow is going to be dependent on what you’re using for your websocket server & authentication / user pool. It may be more useful if you share architecture / code for your specific setup. With that said, I’m doing this; 1. Each time a player opens the browser, I check to see if they are authenticated. (Do they have a valid auth token) 2. If the player does have a valid auth token then jump right to an authenticated connection
(let [connection-url
(cond-> ws-url
(some? credentials)
(str "?token=" credentials)
(nil? credentials)
(str "?token=anonymous"))
set-new-connection
#(let [ws (new js/WebSocket connection-url)]
(doto ws
(aset "onopen" (partial ws-on-open ws on-open))
(aset "onclose" (partial ws-on-close ws on-close config))
(aset "onmessage" (partial ws-on-message ws))
(aset "onerror" (partial ws-on-error ws on-error))
(aset "connection-identifier" connection-identifier))
(reset! last-connection-attempt (time-now))
(reset! conn ws))])
3. On the server, before the authenticated connection is created, the credentials are checked against the user pool to identify the player.
4. The player can now send / receive messages over the websocket.
You would adjust the onopen
onclose
onmessage
onerror
handlers to work with your app.
Not sure what you’re using for your websocket server, but I’ve had luck using https://github.com/ptaoussanis/sente and http-kit (one of its supported web servers) before.@oconn thanks for your answer and the snippet. My current setup is core.async channels for the connections, a http-kit server, chord.http to wrap the websocket handler. I will be switching from compojure to reitit since I need to rework the logic to enable authentication (using buddy). With 3. you mean the server opens a new connection to the client or do you use the incoming one from the client?
Always use the incoming connection from the client. Because of what I’m using to manage connection pooling, websocket connections make a request to the ws endpoint and if they don’t provide authentication details (in my case this is a short lived token) they get an anonymous connection with limited privileges. If they do provide the token then I associate the connection information with that user. This is probably going to be much different for you (and possibly simpler in some ways) because you’re in control of everything (user pool, http server, authentication libs). You could probably use one secure connection and associate that connection with a user after they have authenticated, or the server has determined that they are authenticated.
I’m using clojure/reitit/integrant on the backend und use sockets to connect to a clojurescript/html frontend
I’m using clojure/reitit/integrant on the backend und use sockets to connect to a clojurescript/html frontend
So not really libraries I guess
What do you use for the rendering?
I just render html divs
oh, I see
I tried many other solutions but they all seemed a lot of overhead
Have you seen those videos? https://www.youtube.com/watch?v=5n70q7FFZkY&list=PLdSfLyn35ej-n-SnvLkoTwdxhJWnfQ1QN
yes I have seen it. Don’t quite understand why datascript would be needed.
It is not required, but it was used because Datascript was made by the author of the video.
haha
You may also give ThreeJS a try if you want something more graphical.
yes I watch all his videos. too bad that he didn’t continue the series
Are you using it?
I used ThreeJS from JS in the past.
I’m hesitant on relaying on a library. I tried Oakes play-cljc before. It was nice but overkill for a boardgame like I’m doing.
what are you using now
I am not making games at the moment. I used to use Unity.
You might be right to keep it simple.
Nice - yeah i’ve worked on projects in the past that rely on the DOM for rendering. Currently I’m working on a project that uses Phaser 3. I’m also using websockets + transit to pass data. For application state management I’m using Datascript, but for game state management I’m leaning on phaser 3. Keeping it simple is always a good idea in my book :simple_smile:
There is a nice library which I recommend for writing reactive games: https://github.com/oakes/odoyle-rules
^ This looks pretty cool, thanks for sharing
@vincent.cantin Are you using it in production?
Not yet
What will you build
at the moment, what I build is not related to games
@oconn how do you persist your games on the backend
I’m looking for the best way to persist my game state on the backend. Leaning towards JSON in a Postgres. Does anyone have suggestions
AWS DynamoDB
@oconn as JSON or single fields? I’m currently on heroku but don’t like that they mainly offer SQL
You can nest json in dyanmo rows, but I haven’t had a need for that yet.
so you save the whole state in as one string
if you’re working exclusively with JSON it may be worth looking at a DB that has better support for it. > so you save the whole state in as one string In my setup - game state can be hydrated from multiple rows in the DB, but is mostly held in memory.
My state currently is a clojure hash-map held in memory as well. JSON in Postgres would be one option. Is there code published somewhere how you hydrate the state from multiple rows?
The absolute best way depends on context. JSON in a Postgres JSONB column is underrated and is a fine way to get started — it’s a better Mongo than Mongo — and will take you quite far, but may not be the “best” way in general.