DoubleDeez / MDFramework

A multiplayer C# game framework for Godot 3.4 Mono.
https://discord.gg/UH49eHK
MIT License
76 stars 13 forks source link

Support for OnValueChangedEvent and Converter in MDReplicatedMember #62

Closed Beider closed 4 years ago

Beider commented 4 years ago

The Problem

The new MDList feature brought with it the IMDDataConverter interface, this interface is used so that you can have a list of any type you want including custom classes as long as you provide a data converter. A default converter is provided that supports the basic godot types that do not require conversion.

In addition the MDReplicatedCommandReplicator also has support for OnValueChangedEvent which was introduced when the MDClockedReplicatedMember was introduced.

So right now the support for these features look like this:

I think this is confusing and I think most of this functionality should be moved into the MDReplicatedMember if possible so all classes that inherit can support the same functionality.

However to do this we will need one big change, currently MDReplicatedMember does replication by calling Rset and RSetUnreliable directly on the node, all other replicators send their messages through the MDReplicator.

Code from MDReplicatedMember

///<summary>Replicate this value to all clients</summary>
protected virtual void ReplicateToAll(Node Node, object Value)
{
    MDLog.Debug(LOG_CAT, $"Replicating {Member.Name} with value {Value} from {LastValue}");
    if (IsReliable())
    {
        Node.Rset(Member.Name, Value);
    }
    else
    {
        Node.RsetUnreliable(Member.Name, Value);
    }

    LastValue = Value;
}

The MDClockedReplicatedMember on the other hand sends it through the replicator

///<summary>Replicate this value to all clients</summary>
protected override void ReplicateToAll(Node Node, object Value)
{
    MDLog.Debug(LOG_CAT, $"Replicating {Member.Name} with value {Value} from {LastValue}");
    if (IsReliable())
    {
        Replicator.Rpc(REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()),
            GameClock.GetTick(), Value);
    }
    else
    {
        Replicator.RpcUnreliable(REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()),
            GameClock.GetTick(), Value);
    }

    LastValue = Value;
}

The MDReplicatedCommandReplicator is slightly different

protected void ReplicateCommandToPeer(object[] Command, int PeerId)
{
    Replicator.RpcId(PeerId, REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()), GetGameTick(), Command);
}

To allow support for data converters and for the value change event we would need to modify the MDReplicatedMember to send its messages through the replicator just like MDClockedReplicatedMember does. In addition it should be noted that the MDReplicatedCommandReplicator supports both GameClock and no-GameClock modes in the same class. This is would also be possible for MDReplicatedMember if we send the commands through the MDReplicator.

Suggested Solution

I propose that we redo the replicated members so we end up with the following end result

MDReplicatedMember

MDRInterpolatedVector2

MDReplicatedCommandReplicator

MDList

Benefits of this change

Example of how a class could be it's own data converter and thus be replicated. You can note there is no need to set the data converter setting since the interface is implemented directly in the class. This change would need to be made for the MDList as well.

[MDReplicated]
protected MyObject CustomObject = new MyObject();

[MDReplicated]
protected MDList<MyObject> ObjectList;

public class MyObject : IMDDataConverter
{
    public string Value1 { get; set; }
    public int Value2 { get; set; }
    public bool Value3 {get; set; }

    public object[] ConvertToObjectArray(object item)
    {
        MyObject obj = item as MyObject;
        return new object[] { obj.Value1, obj.Value2, obj.Value3 };
    }

    public object ConvertFromObjectArray(object[] Parameters)
    {
        MyObject obj = new MyObject();
        obj.Value1 = Parameters[0] as String;
        obj.Value2 = Convert.ToInt32(Parameters[1]);
        obj.Value3 = Boolean.Parse(Parameters[2].ToString());
        return obj;
    }

    public int GetParametersConsumedByLastConversion()
    {
        return 3;
    }
}
DoubleDeez commented 4 years ago

Fixed by #64