-
-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When a player sends an input RPC, the input is serialized, and on receiving the RPC it is deserialized. #251
When a player sends an input RPC, the input is serialized, and on receiving the RPC it is deserialized. #251
Conversation
…d in future godot version and makes the code cleaner (saves 2 typecasts)
_auth_input_props.push_back(pe) | ||
else: | ||
_record_input_props.push_back(pe) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This solves #250 by making use of that variable which is unused anywhere else, yet its obvious it exists for this reason.
As in, other players must know the node types/scenes at least, so when an input comes, it can be deconstructed. This deconstruction is impossible without having the properties of the RPC'd node.
Without this PR, that variable shouldn't even exist, but it exists for this purpose as explained above: For other players to know what its properties are (since it doesnt make sense for clients to have the same scenes, yet dont know what input properties they accept)
@@ -0,0 +1,202 @@ | |||
extends Node | |||
class_name ValueToBytes ##Mayhaps needs to become Autoload so as to use the logger. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am this close to making a godotengine pull request for this (basically static constant which returns the byte size of each variant), but even if I did, it would take a while for merge, and more time for netfox to update to latest version.
Anyhow, this class is required for all sorts of serialization, since the rollback properties of netfox are dynamic. The operations here are as cheap as they can be too. Except I guess the function serialize where I could cache the returned objects once they are once constructed. Anyway, encoding 3 floats (Vector3) shouldn't be noticeable.
# TODO: Default to input broadcast in mesh network setups | ||
if enable_input_broadcast: | ||
rpc("_submit_input", input, NetworkTime.tick) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is bloat to run _submit_input()
via an RPC locally. Because the desired result is setting _inputs[tick]
- which is the first thing which happens at the local function _after_tick()
Invoking this RPC locally is negligent normally, but on serialization it's a serious flaw to locally deserialize the input, when it is already deserialized, and set to _inputs[tick]
and _serialized_inputs[tick]
…on) and deserialization for received input properties.
@@ -0,0 +1,202 @@ | |||
extends Node | |||
class_name ValueToBytes ##Mayhaps needs to become Autoload so as to use the logger. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong, but doesn't encode_var
implement the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tried it thinking it was like bytes_to_var
Let me give it a try, because if true, that entire class is needless 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am reading the godot code below because it seems encode_var
includes the type at the header (so a u_int32
integer is 8 bytes instead of 4 bytes as in this PR)
if (buf) {
encode_uint32(header, buf);
buf += 4;
}
r_len += 4;
switch (p_variant.get_type()) {
case Variant::NIL: {
//nothing to do
} break;
case Variant::BOOL: {
if (buf) {
encode_uint32(p_variant.operator bool(), buf);
}
r_len += 4;
} break;
case Variant::INT: {
if (header & HEADER_DATA_FLAG_64) {
//64 bits
if (buf) {
encode_uint64(p_variant.operator int64_t(), buf);
}
r_len += 8;
} else {
if (buf) {
encode_uint32(p_variant.operator int32_t(), buf);
}
r_len += 4;
}
} break;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok so in the godot internals, the header for strings (NodePath
atm) is 12. Let me test this PR with Strings and PackedTypedArrays.
Because if this PR works with them (hadn't tried), then this PR is good. Otherwise, this custom serialization is needless, as it is automatically done by Godot's internals on sending the RPC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it, and it seems I forgot to take into account the string size, and PackedTypeArray sizes, so they are not serialized properly. For both, I would have to add code which is about serializing arrays, so the header would include the byte length of the content, alongside 1 bit for what type, in value-to-bytes.gd
instead of serializing manually and thus adding extra code to the repo.
I know you are against custom serialization code, so I have to ask: For GDScript serialization to be superior to Godot's Internals, it would have to be written in GDExtension to match its performance.
Assuming I were to make such a PR (this serialization code is moved to GDExtension, and I add new logic for the headers of Strings and PackedArrays), would you accept it? The code in RollbackSynchronizer
will stay the same as this PR's. Feel free to deny for any reason, as it is a huge chore for me to write such a thing (I am a newbie on C++)
If it's size, then we can probably get away with much simpler solutions, like not transmitting the input property names, and just putting the values into an array.
It sounds an easy PR given it is part of #264 , but the real bandwidth saver is diff states, not just omitting some strings (properties). Only with diff states, custom serialization becomes overkill.
So, if I make a PR which adds diff states without serialization included (so, half of #264 lol), how do you suggest I solve the problem of the first full state, as explained in #264?
- Reliable RPC for the first full state, and queue/cache the diff states until the first state is received (skim the code of Diff states fix for missing first full state #267 )
- Authority sends full states (unreliable) until the client sends an ack that he has received a full state, then authority switches to sending diff states
- Project settings option where the user puts the amount of diff states to send in fullly (e.g. 40) before sending diff states, so kinda like input redundancy
I don't follow the point of this PR, please clarify. From what I see, this PR introduces custom logic to serialize inputs into binary. What is the benefit? If it's size, then we can probably get away with much simpler solutions, like not transmitting the input property names, and just putting the values into an array. If we ensure that these values are used in the same order on all peers, we basically achieve the same as this PR. The difference would be that we rely on Godot's internal serialization for transmitting data over RPC, instead of serializing manually and thus adding extra code to the repo. If it's for the recording system, then I think it's a bit early for PR's like this. There is no agreement on how a replay system should look like, where should it reside ( core, dedicated module, something else ), how should it interact with the rest of netfox, or even what would be its exact feature set. With that said, I also don't think that we should change the rollback logic to cater for the replay system - these two are entirely separate systems, and shouldn't affect eachother. For the dependant PR's:
|
This is already achieved in this PR though 🤔 The benefit of this PR (and the subsequent ones) is on the size of the packets received in the PR. idk how to use wireshark but the difference is surely big, please give it a test and count the packets. This will prove or disprove that this serialization is preferred to Godot's internal serialization. Though, as mentioned in one of the comments Anyway, if it's easy for you to count the packet sizes (the real RPC packet sizes transmitted on the network, including the header which is omitted in Godot's parameters), it will prove if this PR is worthy or not. If it is not, I will implement that Array solution as a seperate PR (which reduces
This is implemented as part of #264 - since we know the properties - we know which property is received and which wasn't included. And the one which isn't included gets the value of the previous tick's property.
I fully agree. You had mentioned a new Node type at
It uses all of the serialization logic from this PR - see the 2 classes under the serialization folder. Otherwise it wouldn't be such a small commit
It uses the same serialization code, and the diff states support both vanilla (dictionaries) and serialization (as done in this PR and #254 ) Anyway, made a PR without it: #278
The real input delay is like 5 lines of code. But it depends on this PR because the input delay there also supports custom serialization for the inputs. Anyway, made input delay without this PR: #276
It is a cleanup of previous PRs to unify the input code for dictionaries and serialization, using the same logic instead of seperate as this one does ( |
# TODO: Default to input broadcast in mesh network setups | ||
if enable_input_broadcast: | ||
for picked_peer_id in multiplayer.get_peers(): | ||
_submit_raw_input.rpc_id(picked_peer_id, batched_inputs, NetworkTime.tick) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably needless (rpc_id
to all other peers, instead of rpc
) because @rpc(call_remote
) does exactly that - calls an RPC to everyone except the invoker. I can confirm doing this at the state (#254) is futile since the host is always authority, haven't tested for input but should be the same for input as well.
Closing in favour of #278 because this custom serialization doesn't currently support Godot's serialization isn't that bad for the common use-case, it leads to simply double the bytesize so it's not the end of the world. The bandwidth is saved by diff states, regardless of the technique (custom serialization or not) |
Required for the implementation of a replay manager (#220)
Required for the followup PR to serialize/deserialize states as well #254 #264 (hence completing all of #157)
To explain how input serialization works, basically each input's first 4 bytes have the tick timestamp (int), then 5th byte is the size of the serialized properties' values.
So deserialization knows at what byte this input ends, and the next batched input begins (#221). Then 6th byte to the end is the values of the properties.
Since we know the properties and their types via
and even their order, we only need to serialize their values, and deserialize them in the same order :)
There are 4 use-cases to test (all possible combinations of 2 booleans):
I tested locally and found no errors, even with delay 100ms, but ofc test these at least before merging (inb4 unit tests)
This PR is required for the below PRs: