1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System;
13 using System.Collections.Generic;
14 using System.Linq;
15 using OpenRA.Graphics;
16 using OpenRA.Primitives;
17 using OpenRA.Traits;
18 
19 namespace OpenRA.Activities
20 {
21 	public enum ActivityState { Queued, Active, Canceling, Done }
22 
23 	public class TargetLineNode
24 	{
25 		public readonly Target Target;
26 		public readonly Color Color;
27 		public readonly Sprite Tile;
28 
TargetLineNode(Target target, Color color, Sprite tile = null)29 		public TargetLineNode(Target target, Color color, Sprite tile = null)
30 		{
31 			// Note: Not all activities are drawable. In that case, pass Target.Invalid as target,
32 			// if "yield break" in TargetLineNode(Actor self) is not feasible.
33 			Target = target;
34 			Color = color;
35 			Tile = tile;
36 		}
37 	}
38 
39 	/*
40 	 * Things to be aware of when writing activities:
41 	 *
42 	 * - Use "return true" at least once somewhere in the tick method.
43 	 * - Do not "reuse" activity objects (by queuing them as next or child, for example) that have already started running.
44 	 *   Queue a new instance instead.
45 	 * - Avoid calling actor.CancelActivity(). It is almost always a bug. Call activity.Cancel() instead.
46 	 * - Do not evaluate dynamic state (an actor's location, health, conditions, etc.) in the activity's constructor,
47 	 *   as that might change before the activity gets to tick for the first time.  Use the OnFirstRun() method instead.
48 	 */
49 	public abstract class Activity : IActivityInterface
50 	{
51 		public ActivityState State { get; private set; }
52 
53 		Activity childActivity;
54 		protected Activity ChildActivity
55 		{
56 			get { return SkipDoneActivities(childActivity); }
57 			private set { childActivity = value; }
58 		}
59 
60 		Activity nextActivity;
61 		public Activity NextActivity
62 		{
63 			get { return SkipDoneActivities(nextActivity); }
64 			private set { nextActivity = value; }
65 		}
66 
SkipDoneActivities(Activity first)67 		internal static Activity SkipDoneActivities(Activity first)
68 		{
69 			// If first.Cancel() was called while it was queued (i.e. before it first ticked), its state will be Done
70 			// rather than Queued (the activity system guarantees that it cannot be Active or Canceling).
71 			// An unknown number of ticks may have elapsed between the Cancel() call and now,
72 			// so we cannot make any assumptions on the value of first.NextActivity.
73 			// We must not return first (ticking it would be bogus), but returning null would potentially
74 			// drop valid activities queued after it. Walk the queue until we find a valid activity or
75 			// (more likely) run out of activities.
76 			while (first != null && first.State == ActivityState.Done)
77 				first = first.NextActivity;
78 
79 			return first;
80 		}
81 
82 		public bool IsInterruptible { get; protected set; }
83 		public bool ChildHasPriority { get; protected set; }
84 		public bool IsCanceling { get { return State == ActivityState.Canceling; } }
85 		bool finishing;
86 		bool firstRunCompleted;
87 		bool lastRun;
88 
Activity()89 		public Activity()
90 		{
91 			IsInterruptible = true;
92 			ChildHasPriority = true;
93 		}
94 
TickOuter(Actor self)95 		public Activity TickOuter(Actor self)
96 		{
97 			if (State == ActivityState.Done)
98 				throw new InvalidOperationException("Actor {0} attempted to tick activity {1} after it had already completed.".F(self, GetType()));
99 
100 			if (State == ActivityState.Queued)
101 			{
102 				OnFirstRun(self);
103 				firstRunCompleted = true;
104 				State = ActivityState.Active;
105 			}
106 
107 			if (!firstRunCompleted)
108 				throw new InvalidOperationException("Actor {0} attempted to tick activity {1} before running its OnFirstRun method.".F(self, GetType()));
109 
110 			// Only run the parent tick when the child is done.
111 			// We must always let the child finish on its own before continuing.
112 			if (ChildHasPriority)
113 			{
114 				lastRun = TickChild(self) && (finishing || Tick(self));
115 				finishing |= lastRun;
116 			}
117 
118 			// The parent determines whether the child gets a chance at ticking.
119 			else
120 				lastRun = Tick(self);
121 
122 			// Avoid a single tick delay if the childactivity was just queued.
123 			if (ChildActivity != null && ChildActivity.State == ActivityState.Queued)
124 			{
125 				if (ChildHasPriority)
126 					lastRun = TickChild(self) && finishing;
127 				else
128 					TickChild(self);
129 			}
130 
131 			if (lastRun)
132 			{
133 				State = ActivityState.Done;
134 				OnLastRun(self);
135 				return NextActivity;
136 			}
137 
138 			return this;
139 		}
140 
TickChild(Actor self)141 		protected bool TickChild(Actor self)
142 		{
143 			ChildActivity = ActivityUtils.RunActivity(self, ChildActivity);
144 			return ChildActivity == null;
145 		}
146 
147 		/// <summary>
148 		/// Called every tick to run activity logic. Returns false if the activity should
149 		/// remain active, or true if it is complete. Cancelled activities must ensure they
150 		/// return the actor to a consistent state before returning true.
151 		///
152 		/// Child activities can be queued using QueueChild, and these will be ticked
153 		/// instead of the parent while they are active. Activities that need to run logic
154 		/// in parallel with child activities should set ChildHasPriority to false and
155 		/// manually call TickChildren.
156 		///
157 		/// Queuing one or more child activities and returning true is valid, and causes
158 		/// the activity to be completed immediately (without ticking again) once the
159 		/// children have completed.
160 		/// </summary>
Tick(Actor self)161 		public virtual bool Tick(Actor self)
162 		{
163 			return true;
164 		}
165 
166 		/// <summary>
167 		/// Runs once immediately before the first Tick() execution.
168 		/// </summary>
OnFirstRun(Actor self)169 		protected virtual void OnFirstRun(Actor self) { }
170 
171 		/// <summary>
172 		/// Runs once immediately after the last Tick() execution.
173 		/// </summary>
OnLastRun(Actor self)174 		protected virtual void OnLastRun(Actor self) { }
175 
176 		/// <summary>
177 		/// Runs once on Actor.Dispose() (through OnActorDisposeOuter) and can be used to perform activity clean-up on actor death/disposal,
178 		/// for example by force-triggering OnLastRun (which would otherwise be skipped).
179 		/// </summary>
OnActorDispose(Actor self)180 		protected virtual void OnActorDispose(Actor self) { }
181 
182 		/// <summary>
183 		/// Runs once on Actor.Dispose().
184 		/// Main purpose is to ensure ChildActivity.OnActorDispose runs as well (which isn't otherwise accessible due to protection level).
185 		/// </summary>
OnActorDisposeOuter(Actor self)186 		internal void OnActorDisposeOuter(Actor self)
187 		{
188 			if (ChildActivity != null)
189 				ChildActivity.OnActorDisposeOuter(self);
190 
191 			OnActorDispose(self);
192 		}
193 
Cancel(Actor self, bool keepQueue = false)194 		public virtual void Cancel(Actor self, bool keepQueue = false)
195 		{
196 			if (!keepQueue)
197 				NextActivity = null;
198 
199 			if (!IsInterruptible)
200 				return;
201 
202 			if (ChildActivity != null)
203 				ChildActivity.Cancel(self);
204 
205 			// Directly mark activities that are queued and therefore didn't run yet as done
206 			State = State == ActivityState.Queued ? ActivityState.Done : ActivityState.Canceling;
207 		}
208 
Queue(Activity activity)209 		public void Queue(Activity activity)
210 		{
211 			if (NextActivity != null)
212 				NextActivity.Queue(activity);
213 			else
214 				NextActivity = activity;
215 		}
216 
QueueChild(Activity activity)217 		public void QueueChild(Activity activity)
218 		{
219 			if (ChildActivity != null)
220 				ChildActivity.Queue(activity);
221 			else
222 				ChildActivity = activity;
223 		}
224 
225 		/// <summary>
226 		/// Prints the activity tree, starting from the top or optionally from a given origin.
227 		///
228 		/// Call this method from any place that's called during a tick, such as the Tick() method itself or
229 		/// the Before(First|Last)Run() methods. The origin activity will be marked in the output.
230 		/// </summary>
231 		/// <param name="self">The actor performing this activity.</param>
232 		/// <param name="origin">Activity from which to start traversing, and which to mark. If null, mark the calling activity, and start traversal from the top.</param>
233 		/// <param name="level">Initial level of indentation.</param>
PrintActivityTree(Actor self, Activity origin = null, int level = 0)234 		protected void PrintActivityTree(Actor self, Activity origin = null, int level = 0)
235 		{
236 			if (origin == null)
237 				self.CurrentActivity.PrintActivityTree(self, this);
238 			else
239 			{
240 				Console.Write(new string(' ', level * 2));
241 				if (origin == this)
242 					Console.Write("*");
243 
244 				Console.WriteLine(GetType().ToString().Split('.').Last());
245 
246 				if (ChildActivity != null)
247 					ChildActivity.PrintActivityTree(self, origin, level + 1);
248 
249 				if (NextActivity != null)
250 					NextActivity.PrintActivityTree(self, origin, level);
251 			}
252 		}
253 
GetTargets(Actor self)254 		public virtual IEnumerable<Target> GetTargets(Actor self)
255 		{
256 			yield break;
257 		}
258 
TargetLineNodes(Actor self)259 		public virtual IEnumerable<TargetLineNode> TargetLineNodes(Actor self)
260 		{
261 			yield break;
262 		}
263 
DebugLabelComponents()264 		public IEnumerable<string> DebugLabelComponents()
265 		{
266 			var act = this;
267 			while (act != null)
268 			{
269 				yield return act.GetType().Name;
270 				act = act.ChildActivity;
271 			}
272 		}
273 
274 		public IEnumerable<T> ActivitiesImplementing<T>(bool includeChildren = true) where T : IActivityInterface
275 		{
276 			if (includeChildren && ChildActivity != null)
277 				foreach (var a in ChildActivity.ActivitiesImplementing<T>())
278 					yield return a;
279 
280 			if (this is T)
281 				yield return (T)(object)this;
282 
283 			if (NextActivity != null)
284 				foreach (var a in NextActivity.ActivitiesImplementing<T>())
285 					yield return a;
286 		}
287 	}
288 }
289