Before we go any further, I want to reframe what you’re attempting so it’s easier to discuss long term costs and payoffs. What your CommandExecuter is capable of right now is basically this:
{
ShowVideo();
ShowAnimation();
ShowRewards();
}
Now, there’s something important to notice about these calls. They all accept no parameters and have no return types. Now you are proposing to add parameters and return types so you can do something like this:
{[/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT]
[FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT] AddRewardToInventory(
ChooseReward(
GenerateRandomReward()));
}
Of course, in a real language, like C# you would probably just assign into a variable.
{[/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT]
[FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT] var options = GenerateRandomReward();
var chosen = ChooseReward(options);
AddRewardToInventory(chosen);
}
But the meaning is really not any different. In the first example, see how the chained calls work a little like a pipeline. The return of one method feeds into the input of the next.
At this point, a pipeline is an okay analogy and can accomplish your goals. However, see what happens when we get ambitious … Let’s say we want more control over the components.
{[/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT]
[FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT] var options = GenerateRandomReward();
if (OnlyContainsConsumables(options))
{
var chosen1 = ChooseReward(options);
var chosen2 = ChooseReward(options);
AddRewardsToInventory(chosen1, choosen2);
}
else
{
var chosen = ChooseReward(options);
AddRewardToInventory(chosen);
}
}
Suddenly, a pipeline isn’t good enough. You can see that we need to pass options around to a few more components. Plus, there is a fork in the road. How can this be modeled as a pipeline? Technically, it’s still possible by creating a graph of pipes instead of just a straight line but … now your task to reduce the amount of simple code you need to write has just become a very complex problem. Unless you are writing tools for a team of hundreds, it’s very unlikely you will save time doing this.
If you continue down this road and want to add constants, loops, delegates, etc… you are essentially creating a new language. There’s nothing particularly complex about that as it’s a problem well studied by computer scientists: it’s a parser/interpreter! You can pretty easily create one if you study the interpreter design pattern. But at this point you really need to step back and ask your self why are you doing this?? You already have a perfectly good interpreter right in front of you. It’s called the Unity compiler.
One case where you really need an interpreter is when you are inventing a language that makes sense for the domain you are working in. For instance, you might want a designer to be able to write code that looks like this:
Generate 2 Random Consumables And 1 Random Equipment Then Offer Choice Then Add Choosen To Inventory.
Alternatively, you might be building a kind of application which needs the user to write code. If that’s the case, you have no idea what their requirements are, so you need to parse arbitrary code into meaningful commands.
So there’s my rant about long term implications of your endeavor. If you’re certain you’ll be happy with the passing of values from component to component and won’t try to take this further by adding more flow control, then I think the pipeline idea handles your issue of types. I’ll attempt to show you how …
“Pipes” are basically objects which take an input and give an output. the pipes know their input and output type and can be written to make sure they pass output to a pipe which acepts that output as input. The job of the Executer would be then to wire the commands up. So given the items:
var pipes = new List<IPipe>[/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT]
[FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT]{
new GenerateRandomReward(),
new ChooseReward(),
new AddRewardToInventory()
};
The Executer would do something like
{[/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT]
[FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT][FONT=Helvetica][COLOR=rgb(51, 51, 51)]
[LEFT] for (var i = 0; i < pipes.Length - 1; i ++)
pipes[i].Subscribe(pipes[i + 1]);
}
The trick is in how you implement the pipes.
[code]public interface IPipe
{
void Subscribe(IPipe nextPipe);
}
public interface IInPipe<TIn>
{
void OnNext(TIn item);
}
public abstract class OutPipe<TOut>
{
protected IInPipe<TOut> NextPipe { get; set; }
public void Subscribe(IPipe nextPipe)
{
if (nextPipe is IInPipe<TOut>)
Subscribe(nextPipe as IInPipe<TOut>);
else
throw new InvalidOperationException("Types don't match");
}
public void Subscribe(IInPipe<TOut> nextPipe)
{
NextPipe = nextPipe;
}
protected void SendNext(TOut item)
{
NextPipe?.OnNext(item);
}
}
public abstract class Pipe<TIn, TOut> : OutPipe<TOut>, IPipe, IInPipe<TIn>
{
public abstract void OnNext(TIn item);
}
public class GenerateRandomReward : OutPipe<IEnumerable<Reward>>
{ // ... }
public class ChooseReward : Pipe<IEnumerable<Reward>, Reward>
{ // ... }
public class AddRewardToInventory : IInPipe<Reward>
{ // ... }
Notice how the Executer can work on the objects as IPipes, simply calling Subscribe. It is up to the OutPipe to make sure the pipe that is subscribing to it is of the correct type. If it is, then NextPipe is populated, and calling SendNext from a derived type will pass the output on to the next pipe.
Unfortunately, all of this is an orthogonal problem to what Grozzler and Kiwasi have pointed out already. Even with your current code, you are going to find out that all your commands execute sequentially without interruption. That is to say, everything will happen before Unity is permitted to advance the frame count. In order to have the commands appear to execute in real time, while the frames continue, you need to return control to unity after each small step. This is actually quite a difficult job and is why Unity provides coroutines.
My guess is that you could adapt the pipeline to use coroutines internally either by executing a coroutine in each pipe or by wrapping the whole thing into a coroutine. Either way, it won’t be trivial to work out so it’s probably worth it to reconsider if your generic solution is justified. Why not just write all the combination of coroutines you will need? Depending on the size of your project, it might be a lot faster.