Fork me on GitHub
#clojure-gamedev
<
2021-02-19
>
Fabim14:02:52

Hey I’m developing a game in Clojure. Looking forward to exchanging ideas about best practices.

🎉 9
oconn16:02:33

What are you looking to use?

Fabim17:02:15

I’m using clojure/reitit/integrant on the backend und use sockets to connect to a clojurescript/html frontend

Jakub Zika08:02:08

What are you using on FE? Re-frame etc. or do you paint in canvas?

Fabim11:02:39

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

💯 6
Jakub Zika11:02:17

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

Fabim11:02:24

@UL8KUA286 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` `

Jakub Zika16:02:20

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

Fabim16:02:08

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.

Jakub Zika16:02:56

I remember that i had got some better call time results with condp but dont take it as granted. It is long time ago…

Fabim16:02:07

What are you using Quil for?

Jakub Zika16:02:11

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.

Fabim19:02:08

@UL8KUA286 I guess that means one day I will be using your engine then, ha

😁 3
Vincent Cantin17:02:56

@merklefabian What libraries or tools are you using?

Fabim17:02:38

I’m using clojure/reitit/integrant on the backend und use sockets to connect to a clojurescript/html frontend

Fabim17:02:02

So not really libraries I guess

Vincent Cantin17:02:05

What do you use for the rendering?

Fabim17:02:35

I just render html divs

👍 3
Fabim17:02:20

I tried many other solutions but they all seemed a lot of overhead

Fabim18:02:10

yes I have seen it. Don’t quite understand why datascript would be needed.

Vincent Cantin18:02:42

It is not required, but it was used because Datascript was made by the author of the video.

Vincent Cantin18:02:26

You may also give ThreeJS a try if you want something more graphical.

Fabim18:02:41

yes I watch all his videos. too bad that he didn’t continue the series

Fabim18:02:56

Are you using it?

Vincent Cantin18:02:14

I used ThreeJS from JS in the past.

Fabim18:02:00

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.

Fabim18:02:06

what are you using now

Vincent Cantin18:02:56

I am not making games at the moment. I used to use Unity.

Vincent Cantin18:02:11

You might be right to keep it simple.

oconn18:02:25

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

👍 3
Vincent Cantin18:02:09

There is a nice library which I recommend for writing reactive games: https://github.com/oakes/odoyle-rules

oconn18:02:21

^ This looks pretty cool, thanks for sharing

Fabim18:02:50

@U8MJBRSR5 Are you using it in production?

Fabim18:02:43

What will you build

Vincent Cantin18:02:03

at the moment, what I build is not related to games

Fabim19:02:44

@U1APR44RE how do you persist your games on the backend

oconn20:02:13

AWS DynamoDB

Fabim20:02:47

@U1APR44RE as JSON or single fields? I’m currently on heroku but don’t like that they mainly offer SQL

oconn20:02:53

You can nest json in dyanmo rows, but I haven’t had a need for that yet.

Fabim20:02:37

so you save the whole state in as one string

oconn20:02:22

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.

Fabim23:02:07

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?

oconn00:02:01

Code is not open source but more than happy to look at anything you’re entertaining and can share specifics from my codebase if there is something useful to you there. to be honest, the way we’re storing data in our db is optimized to solve problems you may not run into. 100% agree with paul - your use case should drive your decision. Do you plan on querying the JSON in the db? If so it may make sense to store it as jsonb.

Fabim08:02:48

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.

Fabim14:02:27

@U1APR44RE 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 ~

oconn17:02:48

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.

oconn17:02:23

When it comes to managing the connection pools, we don’t do that. We rely on a 3rd party service for that.

Fabim13:02:19

@U1APR44RE Thanks for your answer. Would you share the code how you do 1. with me, please?

oconn13:02:26

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

Fabim18:02:02

@U1APR44RE 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?

oconn22:02:08

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.

Fabim19:02:55

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

paul.legato23:02:16

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.

👍 9