DialogTask.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
34 using System;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Threading;
38 using System.Threading.Tasks;
39 
42 using Microsoft.Bot.Connector;
43 
44 namespace Microsoft.Bot.Builder.Dialogs.Internals
45 {
52  public sealed class DialogTask : IDialogTask
53  {
54  private readonly Func<CancellationToken, IDialogContext> makeContext;
55  private readonly IStore<IFiberLoop<DialogTask>> store;
56  private readonly IEventProducer<IActivity> queue;
57  private readonly IFiberLoop<DialogTask> fiber;
58  private readonly Frames frames;
59  public DialogTask(Func<CancellationToken, IDialogContext> makeContext, IStore<IFiberLoop<DialogTask>> store, IEventProducer<IActivity> queue)
60  {
61  SetField.NotNull(out this.makeContext, nameof(makeContext), makeContext);
62  SetField.NotNull(out this.store, nameof(store), store);
63  SetField.NotNull(out this.queue, nameof(queue), queue);
64  this.store.TryLoad(out this.fiber);
65  this.frames = new Frames(this);
66  }
67 
68  private IWait<DialogTask> nextWait;
69  private IWait<DialogTask> NextWait()
70  {
71  if (this.fiber.Frames.Count > 0)
72  {
73  var nextFrame = this.fiber.Frames.Peek();
74 
75  switch (nextFrame.Wait.Need)
76  {
77  case Need.Wait:
78  // since the leaf frame is waiting, save this wait as the mark for that frame
79  nextFrame.Mark = nextFrame.Wait.CloneTyped();
80  break;
81  case Need.Call:
82  // because the user did not specify a new wait for the leaf frame during the call,
83  // reuse the previous mark for this frame
84  this.nextWait = nextFrame.Wait = nextFrame.Mark.CloneTyped();
85  break;
86  case Need.None:
87  case Need.Poll:
88  break;
89  case Need.Done:
90  default:
91  throw new NotImplementedException();
92  }
93  }
94 
95  return this.nextWait;
96  }
97 
104  public interface IThunk
105  {
106  Delegate Method { get; }
107  }
108 
113  [Serializable]
114  private sealed class ThunkStart : IThunk
115  {
116  private readonly StartAsync start;
117  public ThunkStart(StartAsync start)
118  {
119  SetField.NotNull(out this.start, nameof(start), start);
120  }
121 
122  public override string ToString()
123  {
124  return $"{this.start.Target}.{this.start.Method.Name}";
125  }
126 
127  Delegate IThunk.Method => this.start;
128 
129  public async Task<IWait<DialogTask>> Rest(IFiber<DialogTask> fiber, DialogTask task, IItem<object> item, CancellationToken token)
130  {
131  var result = await item;
132  if (result != null)
133  {
134  throw new ArgumentException(nameof(item));
135  }
136 
137  await this.start(task.makeContext(token));
138  return task.NextWait();
139  }
140  }
141 
146  [Serializable]
147  private sealed class ThunkResume<T> : IThunk
148  {
149  private readonly ResumeAfter<T> resume;
150  public ThunkResume(ResumeAfter<T> resume)
151  {
152  SetField.NotNull(out this.resume, nameof(resume), resume);
153  }
154 
155  public override string ToString()
156  {
157  return $"{this.resume.Target}.{this.resume.Method.Name}";
158  }
159 
160  Delegate IThunk.Method => this.resume;
161 
162  public async Task<IWait<DialogTask>> Rest(IFiber<DialogTask> fiber, DialogTask task, IItem<T> item, CancellationToken token)
163  {
164  await this.resume(task.makeContext(token), item);
165  return task.NextWait();
166  }
167  }
168 
169  internal Rest<DialogTask, object> ToRest(StartAsync start)
170  {
171  var thunk = new ThunkStart(start);
172  return thunk.Rest;
173  }
174 
175  internal Rest<DialogTask, T> ToRest<T>(ResumeAfter<T> resume)
176  {
177  var thunk = new ThunkResume<T>(resume);
178  return thunk.Rest;
179  }
180 
181  private sealed class Frames : IReadOnlyList<Delegate>
182  {
183  private readonly DialogTask task;
184  public Frames(DialogTask task)
185  {
186  SetField.NotNull(out this.task, nameof(task), task);
187  }
188 
189  int IReadOnlyCollection<Delegate>.Count
190  {
191  get
192  {
193  return this.task.fiber.Frames.Count;
194  }
195  }
196 
197  public Delegate Map(int ordinal)
198  {
199  var frames = this.task.fiber.Frames;
200  int index = frames.Count - ordinal - 1;
201  var frame = frames[index];
202  var wait = frame.Wait;
203  var rest = wait.Rest;
204  var thunk = (IThunk)rest.Target;
205  return thunk.Method;
206  }
207 
208  Delegate IReadOnlyList<Delegate>.this[int index]
209  {
210  get
211  {
212  return this.Map(index);
213  }
214  }
215 
216  IEnumerator IEnumerable.GetEnumerator()
217  {
218  IEnumerable<Delegate> enumerable = this;
219  return enumerable.GetEnumerator();
220  }
221 
222  IEnumerator<Delegate> IEnumerable<Delegate>.GetEnumerator()
223  {
224  var frames = this.task.fiber.Frames;
225  for (int index = 0; index < frames.Count; ++index)
226  {
227  yield return this.Map(index);
228  }
229  }
230  }
231 
232  IReadOnlyList<Delegate> IDialogStack.Frames
233  {
234  get
235  {
236  return this.frames;
237  }
238  }
239 
240  void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
241  {
242  var callRest = ToRest(child.StartAsync);
243  if (resume != null)
244  {
245  var doneRest = ToRest(resume);
246  this.nextWait = this.fiber.Call<DialogTask, object, R>(callRest, null, doneRest);
247  }
248  else
249  {
250  this.nextWait = this.fiber.Call<DialogTask, object>(callRest, null);
251  }
252  }
253 
254  async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
255  {
256  // put the child on the stack
257  IDialogStack stack = this;
258  stack.Call(child, resume);
259  // run the loop
260  IEventLoop loop = this;
261  await loop.PollAsync(token);
262  // forward the item
263  this.fiber.Post(item);
264  // run the loop again
265  await loop.PollAsync(token);
266  }
267 
268  void IDialogStack.Done<R>(R value)
269  {
270  this.nextWait = this.fiber.Done(value);
271  }
272 
273  void IDialogStack.Fail(Exception error)
274  {
275  this.nextWait = this.fiber.Fail(error);
276  }
277 
278  void IDialogStack.Wait<R>(ResumeAfter<R> resume)
279  {
280  this.nextWait = this.fiber.Wait<DialogTask, R>(ToRest(resume));
281  }
282 
283  void IDialogStack.Post<E>(E @event, ResumeAfter<E> resume)
284  {
285  // schedule the wait for event delivery
286  this.nextWait = this.fiber.Wait<DialogTask, E>(ToRest(resume));
287 
288  // save the wait for this event, in case the scorable action event handlers manipulate the stack
289  var wait = this.nextWait;
290  Action onPull = () =>
291  {
292  // and satisfy that particular saved wait when the event has been pulled from the queue
293  wait.Post(@event);
294  };
295 
296  // post the activity to the queue
297  var activity = new Activity(ActivityTypes.Event) { Value = @event };
298  this.queue.Post(activity, onPull);
299  }
300 
301  void IDialogStack.Reset()
302  {
303  this.store.Reset();
304  this.store.Flush();
305  this.fiber.Reset();
306  }
307 
308  async Task IEventLoop.PollAsync(CancellationToken token)
309  {
310  try
311  {
312  await this.fiber.PollAsync(this, token);
313 
314  // this line will throw an error if the code does not schedule the next callback
315  // to wait for the next message sent from the user to the bot.
316  this.fiber.Wait.ValidateNeed(Need.Wait);
317  }
318  catch
319  {
320  this.store.Reset();
321  throw;
322  }
323  finally
324  {
325  this.store.Save(this.fiber);
326  this.store.Flush();
327  }
328  }
329 
330  void IEventProducer<IActivity>.Post(IActivity item, Action onPull)
331  {
332  this.fiber.Post(item);
333  onPull?.Invoke();
334  }
335 
336  internal IStore<IFiberLoop<DialogTask>> Store
337  {
338  get
339  {
340  return this.store;
341  }
342  }
343  }
344 
349  public sealed class ReactiveDialogTask : IEventLoop, IEventProducer<IActivity>
350  {
351  private readonly IDialogTask dialogTask;
352  private readonly Func<IDialog<object>> makeRoot;
353 
354  public ReactiveDialogTask(IDialogTask dialogTask, Func<IDialog<object>> makeRoot)
355  {
356  SetField.NotNull(out this.dialogTask, nameof(dialogTask), dialogTask);
357  SetField.NotNull(out this.makeRoot, nameof(makeRoot), makeRoot);
358  }
359 
360  async Task IEventLoop.PollAsync(CancellationToken token)
361  {
362  try
363  {
364  if (this.dialogTask.Frames.Count == 0)
365  {
366  var root = this.makeRoot();
367  var loop = root.Loop();
368  this.dialogTask.Call(loop, null);
369  }
370 
371  await this.dialogTask.PollAsync(token);
372  }
373  catch
374  {
375  this.dialogTask.Reset();
376  throw;
377  }
378  }
379 
380  void IEventProducer<IActivity>.Post(IActivity item, Action onPull)
381  {
382  this.dialogTask.Post(item, onPull);
383  }
384  }
385 
391  {
392  private readonly IPostToBot inner;
393 
395  {
396  SetField.NotNull(out this.inner, nameof(inner), inner);
397  }
398 
399  async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
400  {
401  try
402  {
403  await this.inner.PostAsync(activity, token);
404  }
405  catch (InvalidNeedException error) when (error.Need == Need.Wait && error.Have == Need.None)
406  {
407  throw new NoResumeHandlerException(error);
408  }
409  catch (InvalidNeedException error) when (error.Need == Need.Wait && error.Have == Need.Done)
410  {
411  throw new NoResumeHandlerException(error);
412  }
413  catch (InvalidNeedException error) when (error.Need == Need.Call && error.Have == Need.Wait)
414  {
415  throw new MultipleResumeHandlerException(error);
416  }
417  }
418  }
419 
420  public sealed class EventLoopDialogTask : IPostToBot
421  {
422  private readonly Lazy<IEventLoop> inner;
423  private readonly IEventProducer<IActivity> queue;
424  public EventLoopDialogTask(Func<IEventLoop> makeInner, IEventProducer<IActivity> queue, IBotData botData)
425  {
426  SetField.NotNull(out this.queue, nameof(queue), queue);
427  SetField.CheckNull(nameof(makeInner), makeInner);
428  this.inner = new Lazy<IEventLoop>(() => makeInner());
429  }
430 
431  async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
432  {
433  this.queue.Post(activity);
434  var loop = this.inner.Value;
435  await loop.PollAsync(token);
436  }
437  }
438 
443  public sealed class PersistentDialogTask : IPostToBot
444  {
445  private readonly IPostToBot inner;
446  private readonly IBotData botData;
447 
448  public PersistentDialogTask(IPostToBot inner, IBotData botData)
449  {
450  SetField.NotNull(out this.inner, nameof(inner), inner);
451  SetField.NotNull(out this.botData, nameof(botData), botData);
452  }
453 
454  async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
455  {
456  await botData.LoadAsync(token);
457  try
458  {
459  await this.inner.PostAsync(activity, token);
460  }
461  finally
462  {
463  await botData.FlushAsync(token);
464  }
465  }
466  }
467 }
DialogTask(Func< CancellationToken, IDialogContext > makeContext, IStore< IFiberLoop< DialogTask >> store, IEventProducer< IActivity > queue)
Definition: DialogTask.cs:59
const string Event
Asynchronous external event
This dialog task loads the dialog stack from IBotData before handling the incoming activity and saves...
Definition: DialogTask.cs:443
The exception representing multiple resume handlers specified for the dialog stack.
Namespace for the Microsoft Bot Connector SDK.
void Fail(Exception error)
Fail the current dialog and return an exception to the parent dialog.
IReadOnlyList< Delegate > Frames
The dialog frames active on the stack.
Definition: IDialogTask.cs:51
Adjust the calling convention from Dialog&#39;s to Fiber&#39;s delegates.
Definition: DialogTask.cs:104
PersistentDialogTask(IPostToBot inner, IBotData botData)
Definition: DialogTask.cs:448
Namespace for internal machinery that is not useful for most developers and may change in the future...
Methods to send a message from the user to the bot.
Definition: PostToBot.cs:50
ReactiveDialogTask(IDialogTask dialogTask, Func< IDialog< object >> makeRoot)
Definition: DialogTask.cs:354
Task PostAsync(IActivity activity, CancellationToken token)
Post an item (e.g. message or other external event) to the bot.
Need
This is the stage of the wait, showing what the wait needs during its lifecycle.
Definition: Wait.cs:58
void Post(Event @event, Action onPull=null)
An Activity is the basic communication type for the Bot Framework 3.0 protocol
Definition: ActivityEx.cs:18
delegate Task StartAsync(IDialogContext context)
Encapsulate a method that represents the code to start a dialog.
Shared properties for all activities
Definition: IActivity.cs:9
A reactive dialog task (in contrast to a proactive dialog task) is a dialog task that starts some roo...
Definition: DialogTask.cs:349
EventLoopDialogTask(Func< IEventLoop > makeInner, IEventProducer< IActivity > queue, IBotData botData)
Definition: DialogTask.cs:424
Task PollAsync(CancellationToken token)
Poll the target for any work to be done.
Namespace for the internal fibers machinery that is not useful for most developers and may change in ...
This dialog task translates from the more orthogonal (opaque) fiber exceptions to the more readable d...
Definition: DialogTask.cs:390
The stack of dialogs in the conversational process.
Definition: IDialogTask.cs:46
The exception representing no resume handler specified for the dialog stack.
Root namespace for the Microsoft Bot Builder SDK.