0

I have an object called Unit. Unit has 2 member functions

void MoveTo(int x, int y) 
void Attack(Unit enemyUnit)

Now, I would like to implement some sort of system to be able to track and recreate the functions of the Unit on a different machine. So if a Unit makes following actions

   1. MoveTo xy 
   2. Attack z 
   3. MoveTo ab

I would ideally like to store these function calls (together with their arguments) into some

List<(Action,Arguments)> executedActions 

serialize the executedActions into JSON and then on the other side I would deserialize.

However I've read that it is NOT good idea to serialize functions, delegates and actions, but rather that some sort of system of Dictionary<string, Action> would be ideal, with string being the actionName being the key (example). So I would have to have a List<ActionName, Arguments> and than on the reciever side I would use dict[ActionName] to get the Action.

Since I have a lot of different functions this seems a bit cumbersome (because they all have different parameters) and redundant. I would have to have several dictionaries because of different arguments problem.

Is there some sort of simpler way ? Or is the dictionary way the proper way ?

IvanGrozny
  • 31
  • 6
  • 2
    You might wanna look into Remote Procedure Calls – Emanuel Vintilă Aug 24 '20 at 16:45
  • I d think about two ways depending on your needs :1) command pattern, if you have a known, finished list of fonctions, and 2) expression trees if there are too much cases to be known in advance – Romka Aug 24 '20 at 17:05
  • What do you mean by the other side? Is this about two systems? Is that why you decide to serialize into JSON first time? – Buddhika Chathuranga Aug 24 '20 at 17:35
  • For why serializing delegates is a bad idea, see [how come BinaryFormatter can serialize an `Action<>` but Json.net cannot](https://stackoverflow.com/a/49139733/3744182). – dbc Aug 24 '20 at 19:01

1 Answers1

1

You can use a command pattern and JSON serialization/deserialization something like this.

You need the notion of "type/class" however, because only data is serialized, not behavior:

public interface ICommand
{
      void PerformAction();
}

public class MoveToCommand : ICommand
{
      public MoveToCommand() { /*required for serialization*/ }
      public MoveToCommand(int x, int y)
      {
         X = x;
         Y = y;
      }
      
      public int X { get; set; }
      public int Y { get; set; }

      public void PerformAction() => /*do something*/;
}

var commands = new List<ICommand>();
commands.Add(new MoveToCommand(x = 20, y = 40));
var commandsJson = JsonConvert.ToString(commands);

//send JSON

//on receiver

var receivedCommands = JsonConvert.Deserialize<List<ICommand>>(commandsJson);
foreach(var cmd in receivedCommands)
     cmd.PerformAction();
Colin
  • 4,025
  • 21
  • 40
  • I think this is how I shall try tp solve my problem. But some more questions - If I dont need to use async, then do I need Task at all ? Or could I have the exection logic (`PerformAction`) as some `Func` or Action ? And is it ok/clean code if my `PerformAction` uses some additional information/variable members from `Unit` (its grandparent) ? These variables are not arguments but I do use them. – IvanGrozny Aug 24 '20 at 18:12
  • @Ivan_Grozny - Task isn't needed, but Func/Action can just be an optional implementation detail. All you really need to do is return void (as in the tweaked code above). The "PerformAction" function can internally do anything you want it to, including using arguments from its grandparent. You'll just want the grandparent to store those arguments in a serializable format and ideally express intended usage of variables in the API design and implementation (e.g. by declaring required parameters in a constructor of the subclass to make the API more intuitive). – Colin Aug 24 '20 at 18:17
  • This is a good solution but, I think I need to rearange my code in order for this to work. The thing is `MoveTo()` or `PerformAction`also uses some `Unit` member variables internally. So in `/*do something*/` I have some `Unit.internalVariable`. Maybe its important that I create these classes inside `Unit` so that they are aware of these internal variables, but this wil further bloat my code... I need to think, but thanks a lot for advice. – IvanGrozny Aug 24 '20 at 18:24