8
\$\begingroup\$

I'm working on a server for a little mobile MMORPG. This consists mainly of two important features: mobs and environment resources like trees, rocks etc.

The world is randomly generated and each time a player jumps to another grid, the server generates new mobs and resources for that area. Those are simply getting stored in dictionaries or lists, but are kept permanently in memory.

The problem here is that we need to find a way to save and load those entities without keeping them alive in memory for the rest of the day. This just causes huge memory usage which breaks the server at some point.

We also can't destroy those entities once a player leaves the area because players could abuse this by leaving/joining an area until rare mobs or resources spawn.

What mechanics would you recommend for dealing with this issue?

\$\endgroup\$
  • 12
    \$\begingroup\$ This really looks like an XY Problem \$\endgroup\$ – Ivan García Topete Nov 1 at 22:38
  • 3
    \$\begingroup\$ How do you add mobs and environment objects? How much ram does 1 or 10/100/1000 take up? My first guess would be that you're loading resources per-instance which is incorrect and is a common mistake for newbie game devs. \$\endgroup\$ – user1306322 Nov 2 at 1:22
  • 2
    \$\begingroup\$ Is your only concern the risk of abuse? Or do you want players to be able to manipulate the zone and have those manipulations persist? E.g. killing one of the monsters, chopping down one of the trees, etc. \$\endgroup\$ – MooseBoys Nov 2 at 5:03
  • \$\begingroup\$ Do you need to save and load those entities? Wouldn't keeping record of how many there are work just as well most of the time, without having to record every individual entity? If you keep them alive for e.g. 10 minutes after the players leave the area, you avoid having the "regeneration" being too jaring or exploitable. \$\endgroup\$ – Luaan Nov 4 at 14:04
36
\$\begingroup\$

First of all, are you sure you really need that? Have you calculated the memory footprint?

A small back-of-the-envelope calculation: A single mob and its state should fit into 100 byte of data. Let's give it a whole kByte, in case you are doing something extraordinary. When a cell has 1000 such entities, it requires a MByte. If your world is 100x100 cells, you would need 10 GByte of RAM. That's a lot, but still within a reasonable order of magnitude. The hoster where I hosted my last multiplayer project offers servers with up to 768 GB of RAM. And that's still not the limit of what's technically possible RAM-wise (if you have a deep budget). But I am just inventing numbers here. You should have much better data about the planned size of your world, its entity density, the memory footprint of each entity and the memory footprint you need for any other features of your game.

But let's say that you did the math on your own and come to the conclusion that the memory footprint will be too much for the kind of server you budgeted for. So you indeed have to get that memory footprint down. In that case you can save a lot of memory by only having those cells in memory which have active players and storing the data of all others on the hard drive.

When you have areas without any online characters in them, suspend that area by persisting the state of all entities to hard drive and removing it from memory. You can store that data in a database or by inventing your own file format.

When a player enters a suspended area, reload it from file/database and initialize all entities according to the suspended data. If you have any timed processes which are supposed to continue even when there is no player nearby (like regrowth of resources), determine how much time elapsed since the cell was suspended and simulate that time accordingly.

\$\endgroup\$
  • 7
    \$\begingroup\$ If you need faster load times, you may be able to "persist to RAM": an object frequently has data that's used during player interaction that doesn't get saved to disk. If you discard that transient information whenever the area goes idle, you may be able to get significant memory savings. \$\endgroup\$ – Mark Nov 1 at 20:54
  • \$\begingroup\$ I'll give you +1 for the frame challenge alone! OP is looking at a really strange angle for the problem \$\endgroup\$ – Ivan García Topete Nov 1 at 22:42
  • 2
    \$\begingroup\$ Also, if you use binary in creative ways, you can substantially lower the size per entity. For example, 16 or 32 bit positions, 8 bit rotations, and a 12 bit movement state is more than enough in most cases. For something like a tree, you could have 8 bits referring to the shape/size of the tree, and 4 bits for the state of the tree (chopped down, contains bird nest, etc.) \$\endgroup\$ – Redwolf Programs Nov 2 at 17:13
  • 2
    \$\begingroup\$ @RedwolfPrograms Higher level optimizations are usually even better. Why bother saving the exact position, rotation, animation state etc.? Nobody is watching. Save only the things you need to have the result believable when the player eventually comes back. If the world topology allows it, all you need is how many animals there are in a given cell and generate them in randomish positions when the player comes back. Maintaining traversability graphs allows for some statistical motion of animal populations without having to track individual animals. Have logical cells with LoD. \$\endgroup\$ – Luaan Nov 4 at 14:09
  • 2
    \$\begingroup\$ @RedwolfPrograms Well, instead of instantly "shelving" a cell, you have a shelving process. During the shelving process, monsters gradually move to low-precision "parked" locations or paths. This shelving process could even have multiple steps; the second iteration could completely randomize the contents and discard it, as "enough time has passed" that the previous state it was in is noise. \$\endgroup\$ – Yakk Nov 4 at 15:52
14
\$\begingroup\$

One way you could solve this problem is not actually storing state on disk, but just setting up your generation code to use a seed for the random number generator, so it generates the same thing for a given area every time the area is generated, deterministically.

Then you just keep the 1000 or so most recently visited areas in memory. When areas are evicted from that cache, they will reset to their default state the next time someone goes there, but it will be the same default state every time without you needing to have saved it anywhere. You can't game the system to force rare things to appear, but the things that do appear will eventually "recharge" if left alone long enough.

If you don't want the rare stuff to always be in the same places, you could mix in the date with your random seed, so that every day each area gets a new default state with different stuff in it.

\$\endgroup\$
  • 1
    \$\begingroup\$ Which would revert any changes the players made--killed mobs and collected resources would return. \$\endgroup\$ – Loren Pechtel Nov 2 at 7:46
  • 2
    \$\begingroup\$ @LorenPechtel: You can likely trim the data needed to persist important parts of the state down to a few bits per entity. \$\endgroup\$ – R.. Nov 2 at 21:10
  • 3
    \$\begingroup\$ @LorenPechtel you can explicitly store all the changes the players made to the "default" state - that's likely to be orders of magnitude less than the full state that the players can see; I mean, doing a million actions is a lot for a player, but recording a million actions requires insignificant storage. \$\endgroup\$ – Peteris Nov 3 at 14:04
  • 2
    \$\begingroup\$ @Peteris You also don't need to store a million distinct deltas, just one: The difference between the starting state and the current one. Each delta can just be combined with the previous one (e.g. "monster X lost 3 health" + "monster X lost 6 health" = "monster X lost 9 health"). \$\endgroup\$ – Fund Monica's Lawsuit Nov 4 at 4:49
  • 1
    \$\begingroup\$ @NicHartley Do you even need to store that much? Non-dead monsters could just return to full health if you leave and return. Then you can just store the "died/collected" time for each monster/resource, and use that to decide whether or not it has respawned yet, and drop the delta entirely once the cell has been unoccupied for longer than the respawn timer \$\endgroup\$ – Chronocidal Nov 4 at 8:45
4
\$\begingroup\$

Save it to the hard drive instead.

Saving large quantities of data in a non-volatile fashion is literally why hard drives were invented. If a player isn't interacting with it anymore, you won't need to rapidly access it, so why keep it in the RAM? Just save it to the your server's hard drive in a text file or something.

\$\endgroup\$
  • 1
    \$\begingroup\$ Some sort of database would probably be more suitable than a text file, but I definitely agree with storing it to disc. \$\endgroup\$ – Sean Burton Nov 2 at 23:09
  • \$\begingroup\$ @SeanBurton NoSQL databases often are made of text files. ;) \$\endgroup\$ – nick012000 Nov 3 at 2:47
  • 2
    \$\begingroup\$ Do you have any example of a NoSQL database that does that? I know many of them store text data in their entries, like the JSON in each MongoDB document, but the actual files on disc that this is stored into are most certainly not plain text. MongoDB, for example, actually stores it's data in BSON which is a binary format. \$\endgroup\$ – Sean Burton Nov 3 at 14:02
1
\$\begingroup\$

Option A: For a project that we worked on, we had utilized grouping. It was one creature (with a model of many creatures), but had many hitboxes. When a hitbox HP became 0, it would divide the creature and create a second or third model.

Option B: Utilize low-poly models. We had to use this a long time ago for massive battles with multiple mobs.

Option C: If you are very invested in keeping multiple mobs with multiple movements then you could move the issue to IT by use elastic servers. Amazon, Google, and Microsoft Azure all offer it; however, you would have to have a more sophisticated area/zoning mechanism. Additionally, the prices for server costs can also increase.

\$\endgroup\$
  • \$\begingroup\$ Of interest: Flyweight design pattern. \$\endgroup\$ – Matthieu M. Nov 4 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.