28 Mar, 2010, Deimos wrote in the 1st comment:
Votes: 0
I'm looking for Ruby-specific advice on how to streamline my vmap. Previously, it had been very small, and my project was written in C, so performance wasn't even an issue. I've drastically increased my world size since then, though, and as I sit here watching Ruby churn away trying to create a gazillion rooms, I know I'm going to have to redesign the system if I want a world this big. So, I was looking for some tips on how to maintain such a vast world without giving up performance. I've toyed around with the idea of only instantiating rooms with interesting things in them (characters, items, etc.), which would save a ton of memory, but I'd still need a way to efficiently display large portions of the map to players (even rooms that aren't "interesting"). This map data (terrain type, x/y coords) has to be stored somewhere, and quickly looked up, since players can be moving quickly through the game world.

So… any smooth ideas? I've perused the code repository and searched the forums to no avail.
28 Mar, 2010, Deimos wrote in the 2nd comment:
Votes: 0
I suppose I could keep multiple arrays, one simply for terrain type, and one for pointers to a smaller subset of instantiated rooms that I duplicated when a given x/y index becomes "interesting"… hmm.
28 Mar, 2010, Runter wrote in the 3rd comment:
Votes: 0
This is an interesting problem for Ruby indeed. My first question is how large of a sample are we talking about?

Large samples (read massive) is something Ruby doesn't do well. If we're talking about millions of objects this may be something you want to venture into Ruby's C land for. I think you could come up with a pure Ruby solution, though. Off the top of my head here's an interesting concept. If you subdivide your large world into smaller grids you can dynamically load them as they're needed seemlessly while having a great majority of your world in database/on file/whatever. These chunks should be relatively large IMO. With Ruby you could make it rather seemless from an accessing perspective as well. You could either cache these chunks forever, for a certain amount of time, or just dump them off as nothing "interesting" is happening in them. I.e., nobody is viewing the region, part of the region, interacting in the region, etc.
28 Mar, 2010, Deimos wrote in the 4th comment:
Votes: 0
I'm talking in the millions of rooms (roughly 1500x1500 or so). This has proved far too many objects. I had to kill the generation process after about 10 mins when I hit about 65% mem :tongue: And those test objects had an extremely small footprint compared to what I'd ideally like a room to encompass.

I've thought about loading chunks dynamically, but after seeing the instantiation performance so far, I'm not sure if I could wake up a chunk in a reasonable amount of time unless they were very small. I'd have to have some kind of prediction algorithm concerning the direction a player was moving, or just wake up all chunks surrounding a player, which might prove a little too intensive. Again, I think that would depend on the size of the chunks, though.

And any solution which involves not instantiating "uninteresting" rooms introduces difficulties in game-wide processes like weather, disasters, mob behavior, etc. But I think those are the least of my worries at this point.
28 Mar, 2010, flumpy wrote in the 5th comment:
Votes: 0
What I would do is generate the map on the fly from your area and discard the objects created unless they met some criterion such as you mentioned (they have things in them, they exist 5 rooms square around the player etc). You would also keep rooms that were visited, for a period of time, or if they met the same criteria. If they did need removing you could have some kind of finaliser that persisted the content when the room was unloaded or something. Dynamic mobs could probably be treated like PC's.

Keeping that many objects in memory is not practical or useful, caching the map may well be all you need.

Are you using an ascii map? Does Ruby treat each ascii char as an object? I am asking because I am genuinely interested and do not know btw

Edit: You could assign some kind of staggered cache time out depending on the distance from the PC the room object for the map has been generated.. the further away, the lower the timeout.
28 Mar, 2010, Runter wrote in the 6th comment:
Votes: 0
Quote
Does Ruby treat each ascii char as an object? I am asking because I am genuinely interested and do not know btw


No.
28 Mar, 2010, flumpy wrote in the 7th comment:
Votes: 0
Runter said:
Quote
Does Ruby treat each ascii char as an object? I am asking because I am genuinely interested and do not know btw


No.


Ok cool, so generating an ascii map would not be as memory intensive to cache as all those objects.
28 Mar, 2010, Runter wrote in the 8th comment:
Votes: 0
Quote
I've thought about loading chunks dynamically, but after seeing the instantiation performance so far, I'm not sure if I could wake up a chunk in a reasonable amount of time unless they were very small. I'd have to have some kind of prediction algorithm concerning the direction a player was moving, or just wake up all chunks surrounding a player, which might prove a little too intensive. Again, I think that would depend on the size of the chunks, though.


I wouldn't think it would be too bad. But the bottom line is you shouldn't have rooms floating around in memory as place holders, anyways. Fetching sectors for printing a map (and caching them) is a lot more reasonable than trying to constantly load a bunch of objects containing many variables that are mostly all set to the same value. (Such as a bunch of lists with nothing in them.)
28 Mar, 2010, KaVir wrote in the 9th comment:
Votes: 0
Deimos said:
So… any smooth ideas? I've perused the code repository and searched the forums to no avail.

Did you see the approach I mentioned here? Over a billion rooms using around 1.4MB, plus a small per-player overhead.
28 Mar, 2010, David Haley wrote in the 10th comment:
Votes: 0
In general you want to compress "uninteresting" data as much as possible, and only expand to more complex data structures when it is "interesting" to do so. What exactly is "interesting" in your game is a function of what exactly your game is doing, but typically are interesting those places where players are moving around and interacting with the world.

KaVir's tiles compress the world information, but note that when he has to he creates the 'virtual room'; that is described in more detail in post #157 of that thread.
28 Mar, 2010, flumpy wrote in the 11th comment:
Votes: 0
I think, and correct me if I am wrong, that in post 157 he was describing GW1, where as his current GW2 implementation has solved that problem by being 'roomless'.

Forgive me if I misunderstood tho.
28 Mar, 2010, Scandum wrote in the 12th comment:
Votes: 0
My wilderness used a shared room approach. There were 20 default rooms for each sector, with a 2000x2000 grid of pointers, each pointing to 1 of those 20 rooms. When a room becomes interesting you create a copy of the room it was pointing to (typically when a player enters the room), and once that copy becomes identical to the default room (player leaves) you destroy it and link it back. The concept can be used with a tile-based approach as well.
28 Mar, 2010, KaVir wrote in the 13th comment:
Votes: 0
flumpy said:
I think, and correct me if I am wrong, that in post 157 he was describing GW1, where as his current GW2 implementation has solved that problem by being 'roomless'.

Yes, I was describing the approach used in Last City (a long-dead mud derived from the GW1 code) - it was still room-based, because too many other things were tied in with the concept of rooms, but the rooms were empty containers that were generated and discarded as you moved around. It still very much felt like a regular room-based wilderness though, and my understanding is that Deimos is looking for a room-based solution.
28 Mar, 2010, quixadhal wrote in the 14th comment:
Votes: 0
One way you might do something like this is to use a compressed format to store the terrain data (such as a graphics file format), and store the rooms themselves in an associative array (hash, mapping, whatever ruby calls it). That way you're only using full objects for rooms that actually exist, and you can instantiate generic wilderness rooms on the fly as people/npc's wander through them, possibly removing them after nothing walks through them for long enough.
28 Mar, 2010, Chris Bailey wrote in the 15th comment:
Votes: 0
I was just using small sections that were only loaded when something interesting was going on, like was mentioned. Each subsection of the map was a "zone" so to speak. For simplicity it was just a 2 dimensional array of sector objects that stored their terrain type, height, etc. The zone itself kept track of the in-map coordinates of each object. I'm not sure how well that would scale though, I never went too far with it.
28 Mar, 2010, Deimos wrote in the 16th comment:
Votes: 0
KaVir said:
Did you see the approach I mentioned here? Over a billion rooms using around 1.4MB, plus a small per-player overhead.

Thanks; that was a very interesting read. I'm a little confused about your use of "tiles" though. Are these kept on disk until needed? It seems like it wouldn't offer any benefit over one big array of terrain types unless either unused tiles are kept on disk, or tiles are reused around the map (in which case, the savings would really be dependent on how your map and tiles are designed).
28 Mar, 2010, KaVir wrote in the 17th comment:
Votes: 0
As I explained in the opening sentence of that post, the default world was represented by an array of 512x512 tiles (that's 262144 tiles), and there were only 256 default tiles. So clearly there was a lot of tile reuse - the entire world was assembled much like a jigsaw puzzle.

Admin could map traditional-style areas over the world, while players could dig tunnels and build houses, but such changes tended to be clustered together. The vast majority of the world used the default tiles.

The mud was based on Merc 2.1, but used quite a lot less memory and cpu than stock Merc.
28 Mar, 2010, Runter wrote in the 18th comment:
Votes: 0
Rehearsal —————————————————————-
Loading 5,000,000 locations. 4.960000 0.160000 5.120000 ( 5.150171)
——————————————————- total: 5.120000sec

user system total real
Loading 5,000,000 locations. 5.070000 0.190000 5.260000 ( 5.276646)
#<Facade:0x0000000220d1e8 @hides=:sector_plains>
"On the Plains"
#<Facade:0x0000000efccc50 @hides=:sector_forest>
"In the Woods"
#<Facade:0x00000012d86d98 @hides=:sector_forest>
"In the Woods"
#<Facade:0x000000152c8f20>
"Something Really Common"


Code can be found here.

Furthermore, when I changed it to actually populate the most common tile for 50% of the tiles:
Rehearsal —————————————————————-
Loading 5,000,000 locations. 3.070000 0.240000 3.310000 ( 3.358078)
——————————————————- total: 3.310000sec

user system total real
Loading 5,000,000 locations. 3.750000 0.200000 3.950000 ( 4.013530)


If a facade can be different from the templates simply add to the singleton-class (metaclass, eigenclass, whatever you want to call it).
Rather raw example follows:
f = Facade.new(:sector_forest)
class << f
attr_accessor :name
end
f.name = "Slightly different forest"


Still fairly lightweight, anyways.

f.name would be "Slightly different forest"
f.sector would still be :sector_forest
28 Mar, 2010, David Haley wrote in the 19th comment:
Votes: 0
Scandum said:
My wilderness used a shared room approach. There were 20 default rooms for each sector, with a 2000x2000 grid of pointers, each pointing to 1 of those 20 rooms. When a room becomes interesting you create a copy of the room it was pointing to (typically when a player enters the room), and once that copy becomes identical to the default room (player leaves) you destroy it and link it back. The concept can be used with a tile-based approach as well.

Not to point out the overly obvious, but a 2000x2000 grid of pointers, assuming 32-bit pointers, is 2000x2000x32 = 128,000,000 bytes.

Just to save some time, in the other thread Scandum eventually stated that he wasn't sure he was using terminology correctly, and what he actually means isn't a full grid of pointers but a "pointer hash grid" (i.e., a hash table) of sorts. At least, that's the sense I made out of it. Clearly, a full grid of pointers would be quite silly here, or at the least a total failure when it comes to saving space.
28 Mar, 2010, KaVir wrote in the 20th comment:
Votes: 0
David Haley said:
Not to point out the overly obvious, but a 2000x2000 grid of pointers, assuming 32-bit pointers, is 2000x2000x32 = 128,000,000 bytes.

I think you mean 2000x2000x4 = 16,000,000 bytes.
Random Picks
0.0/31