ioanb7 ioanb7 - 2 months ago 20
C++ Question

C++ member variable change listeners (100+ classes)

I am trying to make an architecture for a MMO game and I can't figure out how I can store as many variables as I need in GameObjects without having a lot of calls to send them on a wire at the same time I update them.

What I have now is:

Game::ChangePosition(Vector3 newPos) {
gameobject.ChangePosition(newPos);
SendOnWireNEWPOSITION(gameobject.id, newPos);
}


It makes the code rubbish, hard to maintain, understand, extend. So think of a Champion example:

GameObject Champion example

I would have to make a lot of functions for each variable. And this is just the generalisation for this Champion, I might have have 1-2 other member variable for each Champion type/"class".

It would be perfect if I would be able to have OnPropertyChange from .NET or something similar. The architecture I am trying to guess would work nicely is if I had something similar to:

For HP: when I update it, automatically call
SendFloatOnWire("HP", hp);


For Position: when I update it, automatically call
SendVector3OnWire("Position", Position)


For Name: when I update it, automatically call
SendSOnWire("Name", Name);


What are exactly
SendFloatOnWire
,
SendVector3OnWire
,
SendSOnWire
? Functions that serialize those types in a char buffer.

OR METHOD 2 (Preffered), but might be expensive

Update Hp, Position normally and then every Network Thread tick scan all GameObject instances on the server for the changed variables and send those.

How would that be implemented on a high scale game server and what are my options? Any useful book for such cases?

Would macros turn out to be useful? I think I was explosed to some source code of something similar and I think it used macros.

Thank you in advance.

EDIT: I think I've found a solution, but I don't know how robust it actually is. I am going to have a go at it and see where I stand afterwards. https://developer.valvesoftware.com/wiki/Networking_Entities

Answer

Overall conclusion I arrived at: Having another call after I update the position, is not that bad. It is a line of code longer, but it is better for different motives:

  1. It is explicit. You know exactly what's happening.
  2. You don't slow down the code by making all kinds of hacks to get it working.
  3. You don't use extra memory.

Methods I've tried:

  1. Having maps for each type, as suggest by @Christophe. The major drawback of it was that it wasn't error prone. You could've had HP and Hp declared in the same map and it could've added another layer of problems and frustrations, such as declaring maps for each type and then preceding every variable with the mapname.
  2. Using something SIMILAR to valve's engine: It created a separate class for each networking variable you wanted. Then, it used a template to wrap up the basic types you declared (int, float, bool) and also extended operators for that template. It used way too much memory and extra calls for basic functionality.
  3. Using a data mapper that added pointers for each variable in the constructor, and then sent them with an offset. I left the project prematurely when I realised the code started to be confusing and hard to maintain.
  4. Using a struct that is sent every time something changes, manually. This is easily done by using protobuf. Extending structs is also easy.
  5. Every tick, generate a new struct with the data for the classes and send it. This keeps very important stuff always up to date, but eats a lot of bandwidth.
  6. Use reflection with help from boost. It wasn't a great solution.

After all, I went with using a mix of 4, and 5. And now I am implementing it in my game. One huge advantage of protobuf is the capability of generating structs from a .proto file, while also offering serialisation for the struct for you. It is blazingly fast.

For those special named variables that appear in subclasses, I have another struct made. Alternatively, with help from protobuf I could have an array of properties that are as simple as: ENUM_KEY_BYTE VALUE. Where ENUM_KEY_BYTE is just a byte that references a enum to properties such as IS_FLYING, IS_UP, IS_POISONED, and VALUE is a string.

The most important thing I've learned from this is to have as much serialization as possible. It is better to use more CPU on both ends than to have more Input&Output.

If anyone has any questions, comment and I will do my best helping you out.

ioanb7