Skip to content
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

NetworkedController & SceneRewinder #37200

Closed
wants to merge 251 commits into from

Conversation

AndreaCatania
Copy link
Contributor

@AndreaCatania AndreaCatania commented Mar 21, 2020

ezgif com-video-to-gif(29)

This PR adds the possibility to synchronize a scene and the Characters between many peers.

It uses a protocol to keep in sync the clients with the server; under the hood it heals the internet problem like (Latency / Packet loss / Packet reorder / Packet Duplication / ...). Networking is complex and get it right is even more difficult; however, this controller want to be most simple to use possible but also most general purpose possible. Indeed, it's possible to use it to for 2D and 3D and with scene of any kind.

Disambiguation

  • The NetworkedController collects the player inputs and use those to control a character, a node, you name it. The NetworkedController takes care to propagate those inputs to all peers.
  • The SceneSynchronizer make sure to keep in sync, all the specified nodes, between all the peers.

How to use it

Here a sample project: https://github.com/GodotNetworking/example-project

To use it, you need to extend the SceneSynchronizer and create an auto-load with this script. Mine is called NetworkSync. Perfect, now you are ready to sync anything into your scene (more about how, below).

Networked Character

Now is time to make your Character networked; add the node NetworkedController and attach a script. Implement these callbacks:

  • func collect_inputs(delta: float): Collects the frame inputs.
  • func controller_process(delta: float): Process the frame inputs.
  • func count_input_size(inputs: Object) -> int: Count the input buffer size.
  • func are_inputs_different(inputs_A: Object, inputs_B: Object) -> bool: Compare two input buffer.
    At this point the NetworkedController is able to control the character. What is needed, however, is to specify the parameters that we need to keep in sync, for this Character; we can use the NeworkSync to do it.

My Character is really simple. I just need to sync the velocity and the translation, so into the _ready function (on the NetworkedController script), I've:

func _ready():
	controlled_character = get_node(controlled_character_path)
	assert(controlled_character != null)

	# Notify the NetworkSync who is controlling `controlled_character` nodes.
	NetworkSync.set_node_as_controlled_by(controlled_character, self)
	NetworkSync.register_variable(controlled_character, "translation")
	NetworkSync.register_variable(controlled_character, "horizontal_velocity")

Note: If your character uses an acceleration, or vertical_velocity, (or anything that is used to compute the final movement), you must register those too.

Scene synchronization

The SceneSynchronizer (that I'm using via the auto-load NeworkSync), is responsible to keep in sync the various objects into the scene.
This mean that you can also keep in sync things that are not directly related to a controller; in the sample, you can find the SyncMesh node, that is moved via the AnimationPlayer and uses the SceneSynchronizer to keep it in sync across the pears.

You can tell which variable you need to keep in sync using the function SceneSynchronizer.register_variable(node, variable_name, on_change_callback = "").
Additionally you can specify a process function, to be called in sync using: SceneSynchronizer.register_process(node, function_name).

Conclusion

There would be much more to talk about and to show, but for the purpose of this PR it should be enough. Please ask questions, propose improvements :) .

This work has been kindly sponsored by IMVU.

Bugsquad edit: This closes godotengine/godot-proposals#631.

@AndreaCatania AndreaCatania added this to the 4.0 milestone Mar 21, 2020
@akien-mga akien-mga requested review from reduz and removed request for neikeq March 21, 2020 09:02
@realkotob
Copy link
Contributor

This is great! Client side compensation and prediction always takes some time to get running.

It would be really nice if you can provide a sample project so we can test it out, the PR description is a bit too abstract to start working with.

@Calinou
Copy link
Member

Calinou commented Mar 21, 2020

This sounds great!

Is CharacterNetController expected to be used only for objects controllable by players, or should it also be used for other kinds of networked physics (such as RigidBodies that can be pushed around by networked players)?

@AndreaCatania
Copy link
Contributor Author

AndreaCatania commented Mar 21, 2020

@asheraryam Yep, I'll submit my test sample project :) and I'll write a more in depth explanation of how it works.

@Calinou This got designed mainly for situations where there is one client that controls the object and the server that has the final authority. The object can be a 2D sprite, a rigid body or a kinematic character.

However, for situations where the object can be controlled by anyone (see the ball in Rocket League) the character net controller is not enough and you may need to study a specific mechanism, for your use case, to get it right.

/// are sent in an unreliable way.
int max_redundant_inputs;

/// The server is behing several frames behind the client, the maxim amount
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

behing. Typo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@jonbonazza
Copy link
Contributor

This is extremely feature complete and i love that you took the time to implement compression in a way that minimizes bandwidth.

My only complain with this is that you are required to extend it. As a built in node, id expect it to just work out of the box and allow me to customize it only with exported properties.

Is there a way we can make this happen?

@jonbonazza
Copy link
Contributor

I also don't see where replay_snapshots is being called, unless I am missing something. Does this currently support server reconciliation?

@AndreaCatania
Copy link
Contributor Author

AndreaCatania commented Mar 26, 2020

I didn't have the time to send the sample project yet. however, server reconciliation is working and integrated using the state check function when the two snapshots are different. Using the reply_snapshots, if the check fail, the client is bring back in the future.

The first idea of this controller, which is the reason why I can submit this pr, is ti make it most customizable possible and usable for any use case. For this reason you still need to extend it.

Now there is the SceneRewinder that does it.

@jonbonazza
Copy link
Contributor

I understand the need for flexibility, it's just that no other node currently works this way to my knowledge, and afaik, there's no way to message in the editor that extending this node is required. Happy to hear others' thoughts here.

@AndreaCatania
Copy link
Contributor Author

Yes, I know, the reason for this is that I need to take inputs and the snapshots information and the most polish way that I found was taking these by calling some predefined functions that must be overridden in GDScript.

Using events and an API to submit these would have make things much more weird and much more prone to error; with my solution the possibility to make mistakes are much lower because the user is never involved in making this mechanism works as intended (with the only exception of replay_snapshots).

@Calinou
Copy link
Member

Calinou commented Mar 26, 2020

there's no way to message in the editor that extending this node is required.

We might be able to do this with a node configuration warning that would display until the node is extended.

@AndreaCatania
Copy link
Contributor Author

AndreaCatania commented Mar 26, 2020

@jonbonazza
Copy link
Contributor

As I am still rather unfamiliar with the inner networking bits of Godot, do we already use ENET's built-in compression tech? Do we need to do compression here as well or is it enough to simply optimize the byte stream before compression?

@Calinou
Copy link
Member

Calinou commented Mar 31, 2020

@jonbonazza Yes, though we also provide FastLZ, zlib and Zstandard compression in NetworkedMultiplayerENet:

CompressionMode

Compression is currently disabled by default. I think it'd be better to enable some kind of compression by default, though we need to do some stress testing to determine which algorithm is the most suited. On paper, Zstandard is the best one, but we may be sending small packets (< 4 KB). However, Zstandard doesn't perform well on small data sizes without a pre-trained dictionary.

Edit: Pull request opened (#38313).

@AndreaCatania
Copy link
Contributor Author

Optimize the stream before sending it is good for two reasons:

  • during the optimization phase you lose precision and optimize it beforehand allows you to use that value even in local, avoiding disallignements.
  • usually knowing the meaning of a value allows to optimize it much more, so the data optimized beforehand are usually smaller.

However, the two methods doesn't exclude each other and can be combined.

@jknightdoeswork
Copy link

Just chiming in to say that this should be heavily documented! Let me know if you want to talk about this sometime, I can contribute documentation provided I can get clarification on some details.

@AndreaCatania
Copy link
Contributor Author

@jknightdoeswork oh, this is really awesome! I'm in the phase of adding another feature to this and so I plan to change a bit how the controller works. But definitely I'll contact you in few weeks.

@jonbonazza
Copy link
Contributor

This PR is becoming increasingly difficult to follow now that we are starting to add other features to it. Can we break the scene_rewinder stuff out into a separate pr?

@AndreaCatania
Copy link
Contributor Author

This PR is becoming increasingly difficult to follow now that we are starting to add other features to it. Can we break the scene_rewinder stuff out into a separate pr?

I'll write here when I've done, so you can review it once finished. I'm sorry but I can't break in two different PRs because the SceneRewinder will also change how the state check is done on the controller and would not make much sense split it.

@AndreaCatania AndreaCatania marked this pull request as draft April 14, 2020 05:45
@AndreaCatania
Copy link
Contributor Author

Hello, I'm closing this PR as I moved all the code into its own repository that is accessible here: https://github.com/GodotNetworking/network_synchronizer

In this repository you can find the version of this PR that works for the godot version 3.x and 4.0, I'll keep developing this module into that repository.
Huge thanks to @Shatur for some of the features and for unit test, and all of you for commenting and suggesting changes to it.
Open an issue or a discussion if you have any question or you need help with this module. 🤙

@theromis
Copy link
Contributor

theromis commented Feb 13, 2023

Sorry if I'm asking about wrong stuff.
According to this godotengine/godot-proposals#3459 similar and #55950 functionality already a part of godot 4.0 dev. Does network_synchronizer module still makes sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add network-enabled actors