[HN Gopher] Building a Distributed Turn-Based Game System in Elixir
___________________________________________________________________
Building a Distributed Turn-Based Game System in Elixir
Author : itsjloh
Score : 317 points
Date : 2021-04-30 03:27 UTC (19 hours ago)
(HTM) web link (fly.io)
(TXT) w3m dump (fly.io)
| henrysdev wrote:
| Great article. I'm currently building a turn-based multiplayer
| improv piano game for the web using Elixir/Phoenix.
|
| In a nutshell, you compete with other players on creating
| freestyle piano solos over backing tracks using a MIDI keyboard.
| Players and audience members then listen through the solos and
| vote for their favorite.
|
| The Elixir/Phoenix stack has been an absolute superpower for
| building my game backend. Some examples: -
| Phoenix channels as a wonderful abstraction over
| managing WS connections. Implementing basic chat took ~3
| minutes on the backend. - The ability to model game
| logic with a finite state machine in a GenServer. The
| lifecycle of a game round is progressed forward by
| receiving incoming client events. - The ability to
| have many game servers running simultaneously as a
| dynamic pool under a DynamicSupervisor. Games can end
| and new games can be created, all isolated and under
| watchdog-like supervision. - ETS as an
| out-of-the-box memory cache for session data.
| Persisting user data between pages and sessions without
| needing to deal with an actual persistence layer.
|
| For the curious, the project is called Midi Matches and is
| currently in public alpha:
|
| Game (desktop chromium only): http://midimatches.com/
|
| Gameplay Video: https://www.youtube.com/watch?v=UVD2wOCB_jE&t=41s
|
| Repo: https://github.com/henrysdev/midimatches
| POiNTx wrote:
| How would you handle animations and more interactive page
| elements? Is it possible to reach the same kind of UI as
| something built with Svelte/Vue/React or are there certain
| limitations?
| mrkurt wrote:
| If you're within ~50ms of the liveview server you can get
| really close to client side interaction responsiveness.
|
| Animations are still better off handled client side, though you
| can go far with web components and liveview updated HTML:
| https://github.com/hmans/three-elements
| 1_player wrote:
| Don't need to be so close. AFAIK when Live View was
| announced, Jose Valim and Chris McCord tested a 60 fps server
| side rendered animation from across the pond and it was
| perfectly smooth.
|
| Definitely not it's intended use case, but it works perfectly
| on regular Internet latencies in the 100-300ms ranges.
| sa1 wrote:
| Server rendered animation sounds less susceptible to
| latency than interactive elements.
| 1_player wrote:
| How so? 60fps means it requires constant updates every
| 16.6ms or you'd get a very noticeable frame delay or
| skip. Again, not really the best use of Live View,
| because it'd be very hard to guarantee that stability for
| all users in different Internet conditions.
|
| Whereas interactive elements just require an update in
| 100ms or 200ms after the user pressed a button, which is
| not that hard to achieve.
| sa1 wrote:
| We're not measuring latency due to processing here. We're
| measuring latency due to round trips.
|
| For server rendered animations, all the frames might be
| delayed due to geographical distance, but the time gap
| between the arrival of individual frames is not affected
| by geographical distance. The server is not waiting for
| the client to request the next frame, it's always
| pushing.
|
| For the user, it's similar to watching a streaming video
| after letting it buffer a bit.
|
| For interactive elements, every interaction has to go
| through the round trip. So the gap between interactions
| is affected by distance.
|
| Server rendered games would suffer much more than server
| rendered animation.
| mcintyre1994 wrote:
| The LiveView is just a HTML page so you can have whatever
| CSS/JavaScript you like on the client side for animations and
| interactivity. Talking to the server is opt-in and you'd
| basically do it wherever you'd usually make an API call from
| React etc. The go-to for JS in LiveView seems to be Alpine
| (https://github.com/alpinejs/alpine) but I haven't got around
| to trying that out myself yet.
| rkangel wrote:
| I'm not sure it's really 'opt in' - talking to the server is
| the default way of changing what's on your screen. Adding a
| whole front end framework is going to conflict with what's
| going on with the LiveView elements. Alpine is the default
| because it's good for "sprinkling" little elements of
| interactivity into an otherwise statically rendered page.
| staticelf wrote:
| You can come a long way with css animations or svg-updates.
| There are limitations and they are mostly connected to the
| latency part.
|
| For example, if you want a 60 fps animation created and the
| server is far far away, like the other side of the globe that
| could be an issue or if your connection is bad.
|
| With liveview, you cannot create offline apps without
| javascript for example. That is a big limitation. But since
| most apps don't really require offline capabilities that is not
| really a big issue.
|
| I have however tried to use a liveview hosted in Sweden from
| Japan (which is basically on the other side of the globe) and I
| didn't experience any noticable latency for simple html
| updates.
|
| I think if you're making a game, you'll still need to use
| javascript. But if you are just making a simple crud app you
| may not need it for at least most of the stuff.
|
| I still think creating modals or things that the user expects
| to be instant should perhaps still be done in javascript but
| for all the server requested data liveview is probably faster
| than your average SPA.
| tomgp wrote:
| I've been thinking of trying out elixir for exactly this kind of
| thing. Having done similar projects in Nodejs (experience was
| fine tbh) and used Xstate or Redux for game state management on
| the server side. What would people recommend for that role in
| Elixir (or is it a case of roll yr own)
| andyjones11 wrote:
| Elixir makes state management very easy by default. Elixir
| implicitly encourages you to write code in-terms of state,
| events and transitions - so managing game state can be done
| using the default tools/abstractions. When I write LiveView
| components I usually end up building a state-machine.
| gallexme wrote:
| There's also gen_statem which provides state machine like
| behavior ontop of genservers
| wut42 wrote:
| gen_statem is a real gem. I've mostly stopped using raw
| genservers now, and build almost everything using it.
| iudqnolq wrote:
| Don't do this unless you've got some experience with
| alternatives. It's definitely not the standard in the
| Elixir community.
|
| Also keep in mind OTP (the Erlang stdlib) is sort of one
| company's kitchen sink helper lib. It's got some gems, but
| do read other people's opinions about which are worth
| using. I wish it's docs included more about when not no use
| it.
|
| Edit:
|
| Definitely look up "contexts". They're the Phoenix word for
| module that handle storing/retrieving a piece of data.
|
| If you're writing an app with low perf requirements
| consider using the db directly for this with no in memory
| persistence. I did this with a low usage internal dashboard
| and it's was dead simple.
|
| Edit2: Thanks to reply, previously said contexts were an
| Elixir concept
| gallexme wrote:
| Contexts are Phoenix specific. They are not the standard
| of the elixir community.
|
| Gen_statem is erlang standard, for state machines
|
| https://hexdocs.pm/gen_state_machine/GenStateMachine.html
| is a a fine wrapper for that
|
| If u have finite states I would go for a state machine
| 1_player wrote:
| Contexts [1] are not a standard nor Phoenix specific. All
| my Elixir projects that deal with data have contexts.
|
| Context is the practice of creating a public API for your
| (database) models, opposite to having your controllers
| access directly your DB and database objects. Gives you
| better testability, better isolation, better code
| architecture.
|
| So instead of (in pseudocode): def
| change_password(user_id, new_password): user =
| User.db.get(id=user_id, is_active=True)
| user.set_password(hash(new_password))
| user.save() send_password_changed_email(user)
| render(password_changed.html, user)
|
| you'd do: def change_password(user_id,
| new_password): # The Accounts module hides all
| the complexity and implementation details user
| = Accounts.get_user_by_id(user_id)
| Accounts.change_user_password(user, new_password)
| render(password_changed.html, user)
|
| That's really just it. It's a best practice which is
| prominent in the Ecto and Phoenix docs, but not
| necessarily applicable to those libraries only, or to
| Elixir only in fact.
|
| 1: https://hexdocs.pm/phoenix/contexts.html
| iudqnolq wrote:
| Thanks for the correction in contexts. I still wouldn't
| recommend state machines to a newcomer.
| gallexme wrote:
| i only mentioned it cause of the use of Redux, which in
| couple areas very similar to a state machine.
|
| state,events, transitions as the parent said, are very
| well buildable with state machines, since thats basically
| what they explicitly are.
|
| and since we are talking about games, it would be more
| logical to use than, contexts, loading/saving state to a
| db is just secondary nature of a game, its more about the
| state transitions and events of player input and game
| logic.
| AlchemistCamp wrote:
| This post is essentially an ad on Fly's blog, but maybe some
| others with experience can weigh in on its tradeoffs vs the other
| popular providers with first class Elixir support.
|
| I haven't used it yet and am curious about new offerings.
|
| That said, the guide linked from this post requires more config
| than the competing options.
|
| For example, Render also handles clustered deployment with
| libcluster setup, and it's very, very simple to set up.
| http://render.com
| mrkurt wrote:
| The thing we do that's (maybe) interesting is run processes all
| over the world. It's helpful for liveview because it minimizes
| latency to end users. Here's an example Liveview cluster
| running in 17 regions, if you scroll down you'll see the round
| trip time to the server you're connected to: https://liveview-
| counter.fly.dev/
|
| Liveview is crazy powerful if you can keep the latency down.
| brightball wrote:
| That is _extremely_ interesting. I 've never seen a cloud
| provider that prominently advertised Anycast IP addresses.
| iudqnolq wrote:
| Fyi at least on mobile the buttons stay focused after tap,
| making it look (incorrectly) like the ack is lagging.
| AlchemistCamp wrote:
| LiveView should already have less latency than an app doing
| traditional AJAX requests, since an open web socket is a lot
| better than making a call to axios!
|
| The less latency the better, though, of course. I'm seeing
| roughly 80ms, which is very good.
| watermelon0 wrote:
| HTTP with keep-alive supports keeping around idle
| connections, which can be used to make subsequent AJAX
| calls.
|
| HTTP/2 has this by default, and allows multiplexing
| multiple requests on a single connection, so even making
| multiple parallel calls won't require establishing new
| connections.
|
| Basically, performance between AJAX and web sockets should
| be comparable in most cases.
| matthewcford wrote:
| I like that you can mount a volume to a region, render can
| only do a single instance.
| msmithstubbs wrote:
| Nice demo! How is the server chosen? I'm seeing ~200ms
| latency. I'm connected to LAX which I don't think would be
| shortest round trip.
| mrkurt wrote:
| Where are you connecting from, out of curiosity? 200ms is
| poor!
|
| Fly apps get anycast IPs, so it's basically bgp getting you
| to our "nearest" edge proxy, then we connect you to the
| closest vm from there.
|
| https://debug.fly.dev will show you which region you hit
| first (the fly-region request header).
|
| --edit--
|
| I snooped on your HN profile and found Australia. We don't
| have an instance of this app up in AU, but we could. You're
| likely connecting to us in Sydney, and LAX is most likely
| the fastest over our backhaul from there.
| avinassh wrote:
| I am connecting from India and my latency is about ~180ms
| mrkurt wrote:
| In a couple of months we'll have instances in Chennai!
| Moru wrote:
| I'm connecting from Sweden and got about 40 ms. But now
| and then it peaks to 4000-8000 ms while my ping within
| the city stays at 2 ms.
| mrkurt wrote:
| Those spikes are hilarious, they're probably from a
| liveview DoS. People are fond of using the debug console
| to "click" hundreds of times per second and there's no
| rate limiting in the app.
| MawKKe wrote:
| From Finland: Through LTE I get served via 'fra' with
| ~60ms (as one might expect). However if I switch to Wifi
| I get 'hkg' with ~260ms?? Not exactly next-door :)
| frafra wrote:
| Same here in Norway :)
| mrkurt wrote:
| Would you mind emailing me with your IP address and a
| screenshot of what you see at https://debug.fly.io when
| you're on wifi? Finland to hkg is not what we want to be
| doing.
|
| kurt fly.io
| iudqnolq wrote:
| (not op) Is that link wrong? I'm getting
| ERR_NAME_NOT_RESOLVED. I tried with Vodafone mobile (UK),
| Openreach Wifi (UK), and a few other countries over a VPN
| (Canada, Australia, ...) in Firefox and Chrome.
| tptacek wrote:
| There's https://debug.fly.dev/.
| mrkurt wrote:
| Oops, yes this.
| msmithstubbs wrote:
| I'm in Brisbane and was expecting Singapore would be
| closer. Thanks for the reply.
| Lio wrote:
| I'm not sure if this is in the realms of what Fly could
| do easily but I love the idea that for this kind of
| Liveview app you could monitor where your users actually
| are dynamically set up a close node to reduce latency.
|
| Bonus points if you could bring the node down again when
| usage drops.
| mrkurt wrote:
| This is actually how our autoscaling works. Since it
| works based on concurrent connections, it should be
| pretty good fore Liveview apps.
|
| https://fly.io/docs/reference/scaling/#autoscaling
| gallexme wrote:
| I'm currently building a distributed mmo emulator using elixir,
| and it's going well (performance/bug/effort wise). Distributed
| via horde/libcluster libraries, ranch as acceptor pool, using
| livebooks for debugging and performance testing.
|
| I couldn't really ask for anything better, as solo dev in less
| than 100h basic distribution solved(one process per map, one per
| player. Maps are spread around the erlang cluster using horde, if
| a map crashes players seeemlesly reconnect in less than a second,
| and the map may actually be on a whole different system,
| especially when the node becomes non viable) Basic monster ai,
| fighting logic. Respawn/monster drops, extensive ddbuggability
| using observer (gui tool included in erlang, allows me to send
| messages to processes/see their status/ what they do, maybe kill
| them in the whole erlang cluster ) Can just use livebooks to do
| more extensive programming (just connect to cluster, execute a
| mark down files code blocks, see results live ingame and in the
| livebook)
|
| It's a very great and easy language for this. I may consider
| moving couple of things to rust using rustler tho for the fun of
| learning about nifs
| Thaxll wrote:
| Looking at your video, performance does not look good though
| which is expected from Elixir, Erlang is a pretty slow
| language.
| gallexme wrote:
| What kind of performance issue do u notice? It's a relatively
| old video, at 500 simultaneous players lna single map it
| takes around 25ms to update the ai+quadtree+ a list update of
| who sees who, meanwhile processing 500 moves + broadcasting
| these Infos to everyone in the view (which are all 500)
|
| This is worst case for it, the other maps do not see any load
| by this and its basically a single core bottleneck, also it's
| very unoptimized code 500 processes sending messages to a
| single map process
| gallexme wrote:
| As soon as the 500 players are spread either around the map
| (so that it doesn't have to handle 500 entities sending
| updates to all other 500 ones ) it takes around 3 ms
|
| Or if they spread across all 70+ maps we see a good
| utilization of every scheduled but still most map
| processing spending most their time idling
| Thaxll wrote:
| The graph we're looking at when it jumps to 75%+ CPU usage
| is it single core? Also using 700MB+ of memory.
| [deleted]
| gallexme wrote:
| there u go a updated video of current version https://cdn
| .discordapp.com/attachments/819516082154242048/83...
|
| 5 times the players(250), tops out at ~35% scheduler
| utilzation
| [deleted]
| dsgrillo wrote:
| This sounds quite interesting!
|
| Any chance you mind sharing that code on GitHub?
| gallexme wrote:
| It's on gitlab. But a lot stuff isn't on it yet/changes a
| lot, and I take my time :)
|
| https://gitlab.com/zen_core/zen_core And u need assets from
| the original game to use it
|
| I made a small example clip to show how I use livebook to
| simulate player load(they use exactly the same genserver as
| real players, just that the packets are already deserialzed )
|
| https://cdn.discordapp.com/attachments/819516082154242048/83.
| ..
| klohto wrote:
| Damn, HN would be the last place where I would expect to
| see Metin. Is the community still there? It was huge in
| Central Europe ~15 years ago.
| gallexme wrote:
| No clue, I left the community around 2010, returned 2014
| when the source been leaked for teaching myself about mmo
| development, I really don't know outside than some
| private servers still having 10+k players and that a few
| legitimate businesses /other games came out from the
| community
| valzam wrote:
| I always think it's amazing how much simpler your architecture
| can get if you don't move any and all state to some other service
| in the name of keeping your app stateless. Of course there are
| good reasons why the industry has moved towards stateless
| business logic services but with all the BEAM goodness
| (resiliency, hot code reload) it might be feasible to write apps
| that actually hold state again.
| mrkurt wrote:
| Apps with state are wonderful to work with. I'm pretty excited
| to build apps that cache in process and don't rely on 47
| external services to do CRUD.
| robotmay wrote:
| Agreed. Elixir/BEAM gives you some really interesting new
| ways of solving problems. A couple of years ago I built a
| subscription system that interacted with Stripe that only
| kept the state whilst a user was interacting with the system
| (used a GenServer as a read-through/write-through Stripe API
| cache that exited after they stopped using it). It was by far
| the least irritating subscription system I've ever worked on.
| cpursley wrote:
| Interesting. Do you mind (and if you can) elaborate on how
| you model that subscription system?
| robotmay wrote:
| Sure thing! Basically I set out to use Stripe's built-in
| tools as much as possible, rather than duplicating the
| state on our side - storing partial data in your own DB
| is one of the biggest pain points of subscription systems
| in my experience. So for this app it didn't have a DB at
| all - Stripe was the source of truth.
|
| The way it worked was fairly straightforward: a user
| would click a link from their account area and it would
| include their Stripe customer ID in the request (all our
| users had already had accounts created due to our main
| booking system). The app then span up a new GenServer to
| represent that customer, which would pull down all the
| data it needed from Stripe's API as soon as it
| initialised. There was also some generic data stored in a
| "global" GenServer to cover stuff like plans etc (I can't
| remember how I had that refreshing, probably via
| webhooks).
|
| Then as the user went through the subscription process or
| management process, any changes they made would be made
| through calls to their own personal GenServer, which on
| write would first write to the Stripe API, then update
| its own cache with the new state returned from Stripe to
| ensure consistency. These GenServers were kept alive by a
| timer set on interactions with it, and would
| automatically shut down 30 minutes after the user stopped
| interacting with it, discarding all the data it held.
| Then when they next return, it fetches the state from
| Stripe again. It also listened for webhooks and would
| update running GenServer instances with data it received
| to ensure they were consistent, and just ignored any for
| users that weren't currently running.
|
| Overall I was really happy with it - it was really
| performant due to the data caching but also didn't suffer
| from staleness issues :)
| piercebot wrote:
| Thanks for taking the time to share, this feels like a
| good approach!
| hwatkins wrote:
| I really like this approach, it seems like it could
| eliminate a lot of complexity. I will have to try it.
| voppe wrote:
| I can't overstate how good Elixir + LiveView is, especially for
| prototyping. I have been experimenting with it ever since it
| first came out, mostly games, and I have to say that building
| even simple multiplayer games is a lot of fun.
|
| An example:
|
| https://dev.voppe.it/chess
|
| (FYI there is no actual game here, just pick emojis, place them
| in the arena and watch them fight to the death via a poorly
| designed combat system. Yes, those placing emojis other than you
| are other players.)
|
| This was a prototype that I built to see if real-time multiplayer
| games were feasible. Apparently LiveView manages high tickrates
| decently enough to be a valid solution for (quasi) real-time
| games. This game for example runs at a whopping 8 ticks/s! It can
| handle more, as I've tried developing with faster tickrates, I
| think as high as 24 t/s, but I want to avoid server strain. With
| enough HTML/CSS/SVG wizardry you can get away with quite a lot.
| But the most amazing thing was the fact that there was no need to
| fiddle around with state syncing. Everything just works! I have
| nothing but praise for it.
|
| Try LiveView if you have the chance!
| laviroman wrote:
| Elixir and especially Phoenix is such a blessing. I recommend
| everybody to try them.
| joelbluminator wrote:
| There are no Elixir jobs where I live, waste of time.
| davydog187 wrote:
| If you work in US timezones, Simplebet is hiring https://jobs
| .lever.co/simplebet/91b15945-fe52-462e-bb8d-a9f9...
| laviroman wrote:
| Then start looking online or start your own business (using
| Elixir because it is the easiest and least money-expensive
| for a single developer) and stop blaming the whole world.
| Internet is everywhere. So, if you can't move to a bigger
| city to find Elixir jobs, look for the remote position,
| working from home is pretty normal for at least a year now.
| Or, as I said, start your own online projects using Elixir.
| Again, using Elixir and its ecosystem is the easiest for a
| single developer. To be honest, people who operate in the
| Elixir ecosystem are often much better programmers (and
| thinkers; a much welcomed side product of functional
| programming) than JavaScript people where the quality of code
| is usually much worse. But it's partially the problem of
| JavaScript where it's easier to write bad code. And Elixir is
| much better especially for big projects than JavaScript.
| Maintaining a big project in JS vs Ex? Elixir is much much
| better at that. Programming things in JavaScript is torture.
| In Elixir it's pleasure. It's like the improved version of
| Ruby, very nice language.
| cpursley wrote:
| Yeah, I don't get it. I've worked remotely with Elixir for
| several years from an non-tech oriented Eastern European...
| And these days it seems like more than half dev jobs are
| remote first.
| [deleted]
| [deleted]
| ashton314 wrote:
| Lots of Elixir gigs are remote these days. My current and
| previous jobs have been remote.
| sfusato wrote:
| https://elixirforum.com/c/community/elixir-jobs/16 (most are
| remote-friendly)
| thawab wrote:
| Trying a new language or framework is fun experiments that
| you spend a few hours on. I have been enjoying implementing
| Message DB[0] in a hobby project. My current job is not in
| programming, and not looking for one.
|
| [0]https://github.com/message-db/message-db
| kinjba11 wrote:
| See also for more discussion on LiveView: Phoenix LiveDashboard
| (April 16, 2020) https://news.ycombinator.com/item?id=22892401
| [deleted]
| edrobap wrote:
| I thought they have used TURN [1] to create a truly distributed
| p2p app. But it turns out that they have build a cluster of
| servers to possibly improve the scalability by distributing load.
| It not clear how they have partitioned the data to distribute
| load. If all state are eventually going to get transferred to
| each node, it may not achieve much by having cluster of nodes.
|
| [1]
| https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_...
| Darmody wrote:
| This is cool. I was thinking of using Elixir/Phoenix for a
| browser game. A different concept, though.
|
| My first choice was node but the more I use Javascript the more I
| hate it.
| Rodeoclash wrote:
| I build the majority of my speculative projects in Elixir these
| days. I did want to try something new but after a few hours of
| fighting NextJS / Typescript / Vercel / Serverside rendering I
| just gave up and went back to Elixir again. It's fantastically
| productive.
| Darmody wrote:
| I'll dive a bit this weekend into it. I was hesitant but
| apparently everybody is super productive and super happy with
| it so I have no choice.
| atonse wrote:
| After clinging to my favorite stack for five years (elixir+ember
| js) my next project will ditch ember.
|
| LiveView is the nail in that coffin. It doesn't help that Ember,
| though I STILL love the technology and framework, is deader than
| dead community-wise except for a few silos. It's very tough to
| find talent. And the benefits just aren't there anymore compared
| to using LiveView. I still use ember on about 8 projects but no
| more.
|
| Have others with this stack reached similar conclusions?
| isuckatcoding wrote:
| I understood all the components but given I don't do a lot of
| complex distributed programming , I kind of got lost about what
| Horde and libcluster do. My world view is very simple: backend
| app server and front end code.
|
| Can anyone ELI5?
| jayunit wrote:
| I'm curious about how race conditions would be handled when
| multiple users, on different regional LiveView servers, take
| conflicting actions.
|
| In the "Let's walk it through" section, it seems like the Player-
| to-LiveView connection will process user input (e.g. a Tic-Tac-
| Toe move) and update the UI to acknowledge this, at which point
| the user can be assured that the LiveView server accepted their
| input. But it seems like this happens before the GameServer has
| also accepted the input. What if Player 2 made a conflicting play
| and their change was accepted by the GameServer before Player 1's
| change reached the GameServer?
|
| Given, in Tic-Tac-Toe, the game is simple enough that this is
| neatly avoided: each regional LiveView server has enough
| information to only allow the current player to make a play. But
| in more complex applications, how might you (anyone; curious for
| discussion) handle this?
|
| One answer is something like: The LiveView server is effectively
| producing optimistic updates, and the GameServer would need to
| produce an authoritative ordering of events and tell the various
| LiveServers which of the optimistic updates lost a race and
| should be backed out.
| laviroman wrote:
| It's the same as with your mobile phone when you lost your wi-
| fi signal. Everything pauses and everybody has to wait.
|
| Have you played games like HOMAM 1 or 2? You can't do anything
| when the CPU is playing its players. You can watch where he
| goes and what he does but that's it. When he is finished then
| you go.
|
| When there is a network error - some message or please wait...
| or loading spinner message should be shown in the meantime.
|
| For turn based RPG games or Chess etc. this is a non issue.
|
| Of course, real time action games etc. is not a good idea for
| this technology.
| 1_player wrote:
| > What if Player 2 made a conflicting play and their change was
| accepted by the GameServer before Player 1's change reached the
| GameServer?
|
| Not sure I understand the question but, I don't see how this
| would happen.
|
| On the BEAM it's processes all the way down. There's a process
| for that instance of the game, which is basically a big state
| machine, and 2 processes representing the client state, one for
| each player.
|
| When the game (process) starts, it expects a message from
| player 1 (process), then one from player 2, and so on.
|
| If there's a client timeout or network disconnection, the
| player process affected crashes, and if the app has been
| architected well, the other player process and game process are
| in a supervision tree, so they crash as well, perhaps notifying
| the other player that the game has ended because of a
| disconnection from the other peer.
|
| But none of this will accept a move from the player 2 when it's
| player 1's turn.
| jayunit wrote:
| Thanks for your reply!
|
| This is very interesting - I'm pretty unfamiliar with BEAM.
| Does this "processes all the way down" span across
| machines/VMs?
|
| From the article, it seemed like there could be two players,
| each connecting to different LiveServer instances (on
| different VMs/hardware in different geographic regions) which
| in turn communicate async via one central GameServer.
|
| In the article, it seems like a message from Player 1 to
| LiveServer 1 doesn't need to wait for the message to also
| reach the central GameServer and be acknowledged before
| LiveServer 1 acks the change back to Player 1. This seems to
| allow races, since the central GameServer is the source of
| truth but the Player1/LiveServer1 communication can complete
| a message/ack round-trip without waiting for acknowledgement
| from the GameServer.
|
| I guess an alternative would be for the system to require a
| message from Player 1 to be passed to LiveServer 1, then
| passed on to the central Game Server which acks back to
| LiveServer 1, which finally can ack back to Player 1 -- this
| means that Player 1 would still need to pay full round-trip
| latency to LS1 and then to the GameServer for any action.
|
| Thanks for any light you can shed on this!
| 1_player wrote:
| Ah, that's indeed a good question, though those are
| implementation details of Fly.io I'm unaware of.
| Ndymium wrote:
| Here's the relevant part from the article:
|
| > The browser click triggers an event in the player's
| LiveView. There is a bi-directional websocket connection
| from the browser to LiveView.
|
| > The LiveView process sends a message to the game server
| for the player's move.
|
| > The GameServer uses Phoenix.PubSub to publish the updated
| state of game ABCD.
|
| > The player's LiveView is subscribed to notifications for
| any updates to game ABCD. The LiveView receives the new
| game state. This automatically triggers LiveView to re-
| render the game immediately pushing the UI changes out to
| the player's browser.
|
| So you can see that when Player 1 does an action, the
| action is sent to the GameServer. Player 1's UI is only
| updated when the GameServer has published the new game
| state via PubSub back to Player 1's LiveView process, that
| pushes it onto the client. So there is the latency of going
| from client to LV to GameServer and back again, but there
| is no race possibility.
| gallexme wrote:
| > I'm pretty unfamiliar with BEAM. Does this "processes all
| the way down" span across machines/VMs?
|
| yes for example if u had a process named on a different
| Machine(Node called in Erlang) called "Alice", u could from
| a different Node send it a message using the Node
| Identifier as additional parameter example:
|
| [coolest_node | _rest_of_nodes] = Node.list()
|
| Process.send({Alice, coolest_node }, :hi)
| egze wrote:
| You could use CRDT for more complex games which are not turn
| based.
|
| https://moosecode.nl/blog/how_deltacrdt_can_help_write_distr...
| mrkurt wrote:
| Your answer is pretty close to what most people do:
| https://en.m.wikipedia.org/wiki/Client-side_prediction
| 1_player wrote:
| There's no need for client side prediction or optimistic UI
| on (most) Live View projects.
|
| It's all done on the server.
| meheleventyone wrote:
| Latency is the reason. Even in a turn based game it still
| feels really bad to make a move and have to wait for it to
| make its way through the round trip before seeing the
| result. In a game with strict ordering like Tic Tac Toe
| there is little reason not to show the chosen move
| immediately.
| 1_player wrote:
| Sure, that's why I meant most use cases.
|
| I mean, 100 ms between a click and a cross appearing on
| screen is not great user experience, but it's not even
| the worst. If you're writing a game, a little client side
| prediction is a good idea.
|
| But if you have a form with instant validation, or any
| old regular UI, that is not necessary at all. The only
| built in optimistic UI functionality on Live View is
| disabling a button when you press it and wait for the
| server to respond, to avoid double submissions.
| meheleventyone wrote:
| > But if you have a form with instant validation, or any
| old regular UI, that is not necessary at all.
|
| Arguably because you're trusting the client and
| essentially the built or built-in behavior is therefore
| optimistic by default. Then hopefully validating on
| submission server-side.
| keymone wrote:
| From the tech talks I vaguely recall, LiveView folks seem
| to disregard latency, which is where the entire model
| falls apart for me because the moment you need more
| control on the client over what to do when the server is
| not responding - you're entirely out of luck.
|
| Though maybe I'm wrong and there has been some new
| developments to address this, I wasn't following too
| closely.
| conradfr wrote:
| One trick I remember using (~two years ago, so early LV)
| when handling click events was to put everything
| async/not needed to reply in a spawn() function.
|
| But yes as soon as you're on the internet you'll often
| feel the delay if your app is interactive.
|
| The problem is that it's a bit random, because the
| network and the VM performances are never totally linear.
|
| I remember implementing a countdown (using 1s
| send_after()) that would work fine most of the time, but
| sometimes there would be some hiccup and the countdown
| would stall just a bit and then process the counter in an
| accelerated fashion, which was terrible from a UI point
| of view, so in the end I did it in JS except for the
| update once the end reached.
| sfusato wrote:
| On the contrary, LiveView documentation acknowledges this
| and suggests to handle such scenarios using client side
| tools:
|
| _There are also use cases which are a bad fit for
| LiveView: Animations - animations, menus, and general UI
| events that do not need the server in the first place are
| a bad fit for LiveView. Those can be achieved without
| LiveView in multiple ways, such as with CSS and CSS
| transitions, using LiveView hooks, or even integrating
| with UI toolkits designed for this purpose, such as
| Bootstrap, Alpine.JS, and similar._
|
| https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.htm
| l#m...
| keymone wrote:
| Sorry, that's not acknowledging that latency can become
| an issue, that's acknowledging that using server-side
| rendering for things that don't require a server isn't
| the best of ideas (shocker, I know).
| hnedeotes wrote:
| What exactly are you thinking in terms of latency
| becoming an issue under LiveView but not on normal
| requests?
|
| Do you mean when websites just need a full refresh
| because they lost their requests on some callback and no-
| one implemented recovery across the 5 levels of callbacks
| or something more specific?
| arjan_sch wrote:
| Well with Liveview you go "full server state" for
| everything that you would normally just use plain JS for.
| For instance, toggling a checkbox or collapsing a div.
|
| Having latency on such lowlevel interactions might make
| the UI feel sluggish as a whole.
| hnedeotes wrote:
| Yeah certainly, but I'm not sure people are using it that
| way?
|
| Most examples are to show cool stuff you can do, they're
| not production vetted. Like most JS examples out there
| don't really mean that people should be publishing live
| credentials with their bundles.
|
| I imagine in most cases one would leave everything that
| is not behind a logged in status as normal routes/pages
| (signin, landing, contacts, etc). Or if not that those
| would be things requiring a socket/real-time interface
| anyway.
|
| For the interactions, I don't think you even need to use
| alpine.js. Plain setup on DOMContentLoad, CSS
| Dropdowns/collapsibles that are replaced on JS load,
| proxying LiveView DOM/Morphdom events (if needed) so
| other components (even vue,react, etc) can listen to
| them, and CSS animations. import {
| setup_live } from "./phx/setup_live.js"; import {
| setup_dropdowns } from "./interactivity/dropdowns.js";
| import { setup_collapsibles } from
| "./interactivity/collapsibles.js"; import {
| setup_links } from "./interactivity/links.js";
| import { setup_inputs } from "./interactivity/inputs.js";
| function setup() { setup_live();
| setup_links(); setup_dropdowns();
| setup_collapsibles(); setup_inputs(); }
| document.addEventListener("DOMContentLoaded", setup);
|
| I went as far as having `onclick` handles and global
| window functions. Complete heresy. Yes, it's not 100% JS
| free, but it's pretty much low overhead.
|
| Then LiveView is mostly for your admin dashboards and
| logged in users views, where it makes it pretty easy to
| do real-time feedback type of interactions/views and spa
| like navigation. Since you have proper auth and
| identification on the user, you can just log them off,
| rate-limit, block an account, and close their socket if
| needed.
| iudqnolq wrote:
| Have you figured out any good way to have per-page
| javascript where the JavaScript is only sent over the
| wire for that pages?
| hnedeotes wrote:
| From the top of my head not really, but this would depend
| on a few things:
|
| - Is it a vendor lib ?
|
| - It's not but is some particular file that is big enough
| to not make sense including on the root layout?
|
| - It's neither, but functionality that can trigger
| multiple times and should be only once and only on those
| pages because it can conflict? or some variation of that?
|
| I think they're all solvable but what makes sense will
| depend on those, but also on how you're using LiveView
| (is it LiveView only for logged-in users/some auth, can
| you set those on the live_view layout...)
|
| But in some cases this is a problem also in spa's, where
| you have to use a snippet to check if the lib has been
| loaded, if not add a script tag to the body, or load it
| through js, etc...
| di4na wrote:
| You don't have to. You can totally use JS for these in
| Liveview
| keymone wrote:
| https://news.ycombinator.com/item?id=26992315
| sfusato wrote:
| You would have latency in all apps that require a server
| round-trip regardless of the stack used.
|
| When you need to go to the server, you go to the server.
| There's no other way around it.
|
| I would be curious to hear how you solve this in other
| stacks? SPAs, whatever, when they need something from the
| server, they reach for the server.
| keymone wrote:
| imagine an SPA for a basic CRUD system. there's a list
| view and details view with a delete button that returns
| you to the list.
|
| in liveview server renders me the list view, i click
| details, server renders me details view, i click delete
| button, server renders me the list view.
|
| if there's big latency/connection error/etc between
| clicking delete and getting back the rendered list - user
| just has to wait.
|
| in spa i could optimistically assume that delete worked
| and render the list that i already have cached without
| the deleted item, allowing user to continue working
| immediately and if there was a disconnect/error - i could
| retry that delete in the background without bothering
| user, only prompting them after some number of retries.
|
| don't see how could i implement this workflow in
| liveview.
| realusername wrote:
| You could just delete the row with alpine or do a 3 line
| JS hook if you wanted to, it's quick and easy. That
| sounds a strange workflow though, it's generally better
| to make users wait for deletion.
| keymone wrote:
| isn't it funny that when you're trying to praise tech you
| like, all sorts of examples jump into mind, but when you
| try criticizing something you like - all that imagination
| vanishes and all existing examples can be dismissed as
| strange :)
| realusername wrote:
| I find it strange because that's not a behaviour I would
| use, but to each their own, it's the beauty of the web
| :).
|
| If you really want to do it, you can add 3 lines on your
| project and that will work with any CRUD page you're
| building, I don't think that's unreasonable or difficult
| to do.
|
| Edit:
|
| Actually thinking about it, if you just made a form for
| that delete button and a phx-disable-with="" on the row
| it would probably work straight away without any JS hook.
| keymone wrote:
| surely you recognize that there is a gap in functionality
| between liveview and fully fledged frameworks that
| provide more granular control over ui interactions?
| realusername wrote:
| Like which other frameworks? You can code that example
| feature you pointed out quicker than in React if you want
| to. You have all the control you want in LiveView.
|
| If you want a delete button per row, no code is needed
| and the phx-disable-with will work out of the box, if you
| want a global delete button on the top which deletes
| multiple rows front-end first before acknowledgement
| (with checkboxes + delete like in Gmail), 5 lines of JS
| maximum in a hook and you're set.
| keymone wrote:
| > You have all the control you want in LiveView
|
| that you can't even acknowledge that there is a gap in
| functionality between liveview, a fairly opinionated
| framework for server side rendering and fully fledged
| client-side frameworks tells me this is not going to be a
| productive conversation, so i'm out, bye
| realusername wrote:
| Have you even used LiveView? It's not opinionated in any
| way, you can do whatever you want with it. It gives you
| extra features to remotely change pages but if you don't
| like having them you don't have to use them at all and
| can plug your favourite JS framework if you want to (or
| you can just use it for parts of the apps and not the
| rest if you want to).
|
| I've worked for years with React and Angular and I don't
| really miss anything with a LiveView-based stack.
| LiveView features gives you 90% of what you want out of
| the box and for the rest it's fine having a bit of JS
| here and there to ensure a good experience.
| sfusato wrote:
| You can do that in LiveView just as easily. Remove the
| item client side, then _pushEvent_ to the server to
| handle the deletion. In case of any errors, notify the
| user, refresh the state .etc
|
| _pushEvent_ , _pushEventTo_ (from client to server) [0]
|
| _push_event_ (from server to client) [1]
|
| [0] https://hexdocs.pm/phoenix_live_view/js-
| interop.html#client-...
|
| [1] https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView
| .html#p...
| keymone wrote:
| > Remove the item client side
|
| so, "just use JS"?
|
| > In case of any errors, notify the user, refresh the
| state .etc
|
| so, "just use JS"?
|
| every time you say "just use JS" you're diminishing the
| usecase of liveview because if i need so much js logic -
| why do i need to also use liveview if i can just use a
| framework/environment where i can share codebase between
| client and server seamlessly and have full control.
| diveanon wrote:
| I did a deep dive into liveview and this was my take
| away.
|
| Its nice tech, but once you start introducing JS again to
| improve UX you really start asking yourself why you
| didn't just build it with react in the first place.
| sfusato wrote:
| You stated _don 't see how could i implement this
| workflow in liveview._. I've presented you a way.
|
| _In case of any errors, notify the user, refresh the
| state .etc_ = > This would all be done server-side and
| the client side would simply react automatically. The
| client side code in this case would have been minimal.
|
| I don't think I'm diminishing anything. For quite a few
| years I was neck deep in React/Vue world. Now that I'm
| actively using LiveView, I can properly compare the
| differences between both approaches, cons and pros. For
| any new project, in the majority of cases I would pick
| Elixir with Phoenix LiveView instead of Elixir/Phoenix
| (backend) with React/Vue (frontend).
| keymone wrote:
| You've presented a workaround and a hack tbh, not
| something natively supported because the workflow doesn't
| map to liveview model, which is fine but you have to be
| honest with yourself and acknowledge when stuff like that
| happens, otherwise you're in for lots of fun down the
| line.
|
| > For any new project, in the majority of cases I would
| pick Elixir with Phoenix LiveView instead of
| Elixir/Phoenix (backend) with React/Vue (frontend).
|
| This could just be recency bias. New tech is always
| exciting, old tech is always linked to memories of all
| the issues you've had in the past.
| sfusato wrote:
| I wouldn't say it's a hack, but I would agree it's not
| the standard way to do things in the LiveView world,
| exactly because latency it's an overstated or
| misunderstood issue. But if you want to do more, LiveView
| gives you the tools.
|
| _> This could just be recency bias. New tech is always
| exciting, old tech is always linked to memories of all
| the issues you've had in the past._
|
| I still maintain some React/Vue apps and work with on a
| daily basis, so it's not a distant memory.
|
| I like choosing the right tool for the right job. For
| example, I would still choose ReactNative over 2
| different code bases for a mobile app for a small team
| that needs to move fast. For the 5% of cases when that
| wouldn't do you would need to go native. I see the
| situation similarly with LiveView. It's hard to beat its
| productivity & power in 95% of use cases.
| Ndymium wrote:
| Possibly many LiveView tech demos / projects by the
| community haven't had much thought into latency, but
| LiveView itself even contains a latency simulator[0]
| built-in. Additionally, it can toggle classes on elements
| when you click them and turn them off again when an
| acknowledgment has been received from the backend [1].
| Finally you have the JS hooks, through which you can just
| implement any kind of loading indication you want on the
| frontend. So the tools are there, they just need to be
| used.
|
| [0] https://hexdocs.pm/phoenix_live_view/js-
| interop.html#simulat...
|
| [1] https://hexdocs.pm/phoenix_live_view/js-
| interop.html#loading...
| ngcc_hk wrote:
| It is turn based and sync is easy, whatever you do unless you
| are the only one in turn you can be safely ignored. Once move
| to non-turn based ...
| robotmay wrote:
| Ever since I heard about Fly I was really keen to try out
| something using Elixir. I've been severely lacking in ideas that
| I can complete within two weeks though (my maximum attention
| span)!
|
| This was well-timed for me - nice to have a reference working
| config to play around with, especially for clustering. How do you
| find the BEAM handles a distributed cluster across multiple
| regions? Are there any oddities that crop up due to inter-site
| latency?
___________________________________________________________________
(page generated 2021-04-30 23:02 UTC)