public static async Task <List <string> > Run( [OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log) { var nameList = context.GetInput <List <string> >() ?? new List <string>(); var addNameTask = context.WaitForExternalEvent <string>(EventNames.AddName); var removeNameTask = context.WaitForExternalEvent <string>(EventNames.RemoveName); var isCompletedTask = context.WaitForExternalEvent <bool>(EventNames.IsCompleted); var resultingEvent = await Task.WhenAny(addNameTask, removeNameTask, isCompletedTask); if (resultingEvent == addNameTask) { nameList.Add(addNameTask.Result); log.Info($"Added {addNameTask.Result} to the list."); } else if (resultingEvent == removeNameTask) { nameList.Remove(removeNameTask.Result); log.Info($"Removed {removeNameTask.Result} from the list."); } if (resultingEvent == isCompletedTask && isCompletedTask.Result) { log.Info("Completed updating the list."); } else { context.ContinueAsNew(nameList); } return(nameList); }
public static async Task <string> RunEngineEventsOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context , ILogger log) { var endSessionEvent = context.WaitForExternalEvent <Compute>("EndSessionEvent"); var addCollaborateEvent = context.WaitForExternalEvent <Collaborator>("AddCollaboratorEvent"); ///todo:implement timeout logic, options: DurableTimer/Event based /// var triggeredEvent = await Task.WhenAny(endSessionEvent, addCollaborateEvent); if (triggeredEvent == endSessionEvent) { var computeToDelete = endSessionEvent.Result; // Delete compute await context.CallActivityAsync <Compute>("DeleteCompute", computeToDelete); log.LogInformation($"Delete instance {context.InstanceId} "); //end session } if (triggeredEvent == addCollaborateEvent) { //call addCollaborator activity //continue as new context.ContinueAsNew(null); } return(context.InstanceId); }
public async Task <ScopeCreepActivityResponse> ScopeCreepOrchestrator([OrchestrationTrigger] DurableOrchestrationContext context) { var request = context.GetInput <ScopeCreepActivityRequest>(); var response = await context.CallActivityAsync <ScopeCreepActivityResponse>("ScopeCreepActivity", request); if (response.Depth < request.DepthRequested) { request = new ScopeCreepActivityRequest() { Depth = request.Depth + 1, DepthRequested = request.DepthRequested }; response = await context.CallSubOrchestratorAsync <ScopeCreepActivityResponse>("ScopeCreepOrchestrator", request); if (response.Depth < request.DepthRequested) { context.ContinueAsNew(request, true); } } await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(5), CancellationToken.None); return(response); }
public static async Task <int> Counter([OrchestrationTrigger] DurableOrchestrationContext ctx) { int currentValue = ctx.GetInput <int>(); string operation = await ctx.WaitForExternalEvent <string>("operation"); bool done = false; switch (operation?.ToLowerInvariant()) { case "incr": currentValue++; break; case "decr": currentValue--; break; case "end": done = true; break; } // Allow clients to track the current value. ctx.SetCustomStatus(currentValue); if (!done) { ctx.ContinueAsNew(currentValue, preserveUnprocessedEvents: true); } return(currentValue); }
public static async Task <string> RunOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log) { var workflowContext = context.GetInput <WorkflowContext>(); log.LogInformation($"CurrentProcess: {workflowContext.CurrentProcess.ToString()}"); switch (workflowContext.CurrentProcess) { case Process.Started: workflowContext = await context.CallActivityAsync <WorkflowContext>("Workflow_Started", workflowContext); break; case Process.Arrived: workflowContext = await context.CallActivityAsync <WorkflowContext>("Workflow_Arrived", workflowContext); break; default: return(workflowContext.Message); } ; context.ContinueAsNew(workflowContext); return(workflowContext.Message); }
public static async Task ManageResources([OrchestrationTrigger] DurableOrchestrationContext context, ILogger log) { context.SetCustomStatus("StartingManageResources"); var templatesWithMetadata = await context.CallActivityAsync <List <BaseTemplate> >("GetTemplatesToManage", null); foreach (var template in templatesWithMetadata.Where(a => a.Subscriptions.Count > 0 && a.QueueSizeToMaintain > 0)) { var count = 0;//queueClient().GetQueueReference(template.QueueName).ApproximateMessageCount ?? var resourcegroupsCreated = await context.CallActivityAsync <int>("GetResourceGroupsCreated", template); log.LogInformation($"Template: {template.Name} has queueSize: {count} and resourcegroupscreated: {resourcegroupsCreated}"); template.CurrentQueueSize = count; template.ResourceGroupsCreated = resourcegroupsCreated; } context.SetCustomStatus("SetItemsToCreate"); var taskList = new List <Task>(); taskList.AddRange(templatesWithMetadata.Where(a => a.ItemstoCreate > 0 && a.QueueSizeToMaintain > 0).Select(a => context.CallSubOrchestratorAsync("CreateATrialResource", new TrialResource { Template = a }))); context.SetCustomStatus("WaitForItemsToGetCreated"); await Task.WhenAll(taskList.ToArray()); context.SetCustomStatus("MissingItemsCreated"); DateTime nextCleanup = context.CurrentUtcDateTime.AddSeconds(10); await context.CreateTimer(nextCleanup, CancellationToken.None); context.ContinueAsNew(null); }
public static async void Run([OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log) { var imageUrlList = context.GetInput <List <string> >(); List <string> tobeProcessed = imageUrlList.Take(PageSize).ToList(); var tasks = new Task <string> [tobeProcessed.Count]; for (int i = 0; i < tobeProcessed.Count; i++) { tasks[i] = context.CallActivityAsync <string>("SingleImageAnalyzer", tobeProcessed[i]); } var stopwatch = Stopwatch.StartNew(); await Task.WhenAll(tasks); stopwatch.Stop(); DateTime nextRound = context.CurrentUtcDateTime.AddMilliseconds(1000 - stopwatch.Elapsed.TotalMilliseconds); await context.CreateTimer(nextRound, CancellationToken.None); var leftOverList = imageUrlList.Skip(PageSize).ToList(); if (leftOverList.Count > 0) { context.ContinueAsNew(leftOverList); } }
public static async Task <int> Counter([OrchestrationTrigger] DurableOrchestrationContext ctx) { int currentValue = ctx.GetInput <int>(); string operation = await ctx.WaitForExternalEvent <string>("operation"); bool done = false; switch (operation?.ToLowerInvariant()) { case "incr": currentValue++; break; case "decr": currentValue--; break; case "end": done = true; break; } if (!done) { ctx.ContinueAsNew(currentValue); } return(currentValue); }
public static async Task TriviaOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, ILogger logger) { Clue previousClue = null; try { previousClue = context.GetInput <Clue>(); var outputs = new List <string>(); var nextRun = context.CurrentUtcDateTime.AddSeconds(secondsBetweenClues); var clue = await context.CallActivityAsync <Clue>(nameof(GetAndSendClue), (previousClue, nextRun)); await context.CreateTimer(nextRun, CancellationToken.None); previousClue = clue; } catch (Exception ex) { logger.LogError(ex.ToString()); } finally { context.ContinueAsNew(previousClue); } }
public static async Task RunOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context) { var populationAndSettings = context.GetInput <PopulationAndSettings>(); var population = populationAndSettings.Population; var tasks = new Task <double> [population.Individuals.Count]; for (int i = 0; i < population.Individuals.Count; i++) { tasks[i] = context.CallActivityAsync <double>("EvaluatePopulation", population); } await Task.WhenAll(tasks); var evaluationResults = tasks.Select(e => e.Result).ToList(); BestIndividual bestIndividual = await context.CallActivityAsync <BestIndividual>("GetBestIndividual", population, evaluationResults); populationAndSettings.GenerationId = await context.CallActivityAsync <int>("SaveBestResult", bestIndividual, populationAndSettings.InstanceId, populationAndSettings.GenerationId); population = await context.CallActivityAsync <Population>("Selection", population, evaluationResults); population = await context.CallActivityAsync <Population>("Crossover", population); populationAndSettings.Population = await context.CallActivityAsync <Population>("Mutation", population); context.ContinueAsNew(populationAndSettings); }
public static async Task <int> Run( [OrchestrationTrigger] DurableOrchestrationContext counterContext, TraceWriter log) { int counterState = counterContext.GetInput <int>(); log.Info($"Current counter state is {counterState}. Waiting for next operation."); string operation = await counterContext.WaitForExternalEvent <string>("operation"); log.Info($"Received '{operation}' operation."); operation = operation?.ToLowerInvariant(); if (operation == "incr") { counterState++; } else if (operation == "decr") { counterState--; } if (operation != "end") { counterContext.ContinueAsNew(counterState); } return(counterState); }
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext context) { var payload = context.GetInput <string>(); await context.CallActivityAsync(nameof(Activity), payload); await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(10), CancellationToken.None); context.ContinueAsNew(null); }
public async Task Run([OrchestrationTrigger] DurableOrchestrationContext context, ILogger log) { var teamModel = context.GetInput <TeamModel>(); teamModel.TimeZoneInfoId ??= await TimeZoneHelper.GetAndUpdateTimeZoneAsync(teamModel.TeamId, _timeZoneService, _scheduleConnectorService, _scheduleSourceService); if (!teamModel.Initialized) { await context.CallSubOrchestratorAsync(nameof(InitializeOrchestrator), teamModel); teamModel.Initialized = true; } // if we haven't got a valid timezone then we cannot sync shifts because we will not be able to properly // convert the shift start and end times to UTC if (teamModel.TimeZoneInfoId != null) { try { var weeks = context.CurrentUtcDateTime.Date .Range(_options.PastWeeks, _options.FutureWeeks, _options.StartDayOfWeek); var weekTasks = weeks .Select(startDate => new WeekModel { StartDate = startDate, StoreId = teamModel.StoreId, TeamId = teamModel.TeamId, TimeZoneInfoId = teamModel.TimeZoneInfoId }) .Select(weekModel => context.CallSubOrchestratorAsync(nameof(WeekOrchestrator), weekModel)); await Task.WhenAll(weekTasks); } catch (Exception ex) when(_options.ContinueOnError) { log.LogTeamError(ex, teamModel); } } else { log.LogMissingTimeZoneError(teamModel); } if (_options.FrequencySeconds < 0) { return; } var dueTime = context.CurrentUtcDateTime.AddSeconds(_options.FrequencySeconds); await context.CreateTimer(dueTime, CancellationToken.None); context.ContinueAsNew(teamModel); }
public static async Task AgentOrchestrator([OrchestrationTrigger] DurableOrchestrationContext context) { var agentRequest = context.GetInput <AgentRequest>(); agentRequest = await context.CallActivityAsync <AgentRequest>("AgentActivity", agentRequest); // await Task.Delay(TimeSpan.FromSeconds(1)); context.ContinueAsNew(agentRequest); }
public static async Task Orchestrator( [OrchestrationTrigger] DurableOrchestrationContext context) { await context.CallSubOrchestratorAsync("ExtractOrchestrator", null); var nextSchedule = context.CurrentUtcDateTime.AddSeconds(60); // It is going to be 10 min. However for testing. await context.CreateTimer(nextSchedule, CancellationToken.None); context.ContinueAsNew(null); }
public static async Task RunOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context) { await context.CallActivityAsync(nameof(SendSimonMoney), 500); // sleep for one hour between sending Simon money DateTime nextCleanup = context.CurrentUtcDateTime.AddHours(1); await context.CreateTimer(nextCleanup, CancellationToken.None); context.ContinueAsNew(null); }
public static async Task RunOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log) { log.LogDebug("Processor starting"); Queue <string> queuedJobs = context.GetInput <Queue <string> >(); string jobId = queuedJobs.Dequeue(); await context.CallActivityAsync("JobProcessor_StartJob", jobId); Task <string> jobCompletedEvent = context.WaitForExternalEvent <string>(JobCompletedEventName); Task <string> timeoutTask = context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(5), jobId, CancellationToken.None); //todo: add a timer to timeout our wait for the job to complete. while (true) { Task <string> queuedEvent = context.WaitForExternalEvent <string>(JobQueuedEventName); Task <string> completedTask = await Task.WhenAny(jobCompletedEvent, queuedEvent, timeoutTask); if (completedTask == queuedEvent) { string queuedJobId = completedTask.Result; log.LogDebug($"Job queued {queuedJobId}"); queuedJobs.Enqueue(queuedJobId); } else { //todo: check that job id matches the one we're waiting for to complete. if (completedTask == timeoutTask) { //todo: do some logging or retry logic here. log.LogDebug($"Job timed out {jobId}"); } else { log.LogDebug($"Job completed {jobId}"); } if (queuedJobs.Count > 0) { context.ContinueAsNew(queuedJobs); //This is required to keep the execution history from growing indefinitely. } break; } } }
public static async Task FlightsOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, ILogger logger) { const int maxIterations = 480; var iteration = context.GetInput <int>(); if (iteration >= maxIterations) { iteration = 0; } try { await context.CallActivityAsync(nameof(GetAndSendData), new IterationInput { Iteration = iteration, DataFileName = $"{containerName}/{iteration}.json" }); } catch (Exception ex) { logger.LogError(ex.ToString()); } finally { if (iteration < maxIterations) { context.ContinueAsNew(iteration + 1); } else { context.ContinueAsNew(0); } } }
public static async Task JsonInput([OrchestrationTrigger] DurableOrchestrationContext ctx) { var input = ctx.GetInput <dynamic>(); if (input != null) { string helloTest = input.Hello; } var data = await ctx.WaitForExternalEvent <dynamic>("parse"); string nameTest = data.Name; ctx.ContinueAsNew(data); }
public static async Task <ScalingState> ScalingLogic([OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log) { var state = context.GetInput <ScalingState>(); log.Info($"Current state is {state}. Waiting for next value."); var metric = await context.WaitForExternalEvent <Metric>(nameof(Metric)); var history = state.History; log.Info($"Scaling logic: Received {metric.Name}, previous state is {string.Join(", ", history)}"); // 2. Add current metric value, remove old values history.Add(metric.Value); history.RemoveAll(e => e.Time < metric.Value.Time.Subtract(Period)); // 3. Compare the aggregates to thresholds, produce scaling action if needed ScaleAction action = null; if (history.Count >= 5 && context.CurrentUtcDateTime - state.LastScalingActionTime > CooldownPeriod) { var average = (int)history.Average(e => e.Value); var maximum = history.Max(e => e.Value); if (average > ThresholdUp) { log.Info($"Scaling logic: Value {average} is too high, scaling {metric.ResourceName} up..."); action = new ScaleAction(metric.ResourceName, ScaleActionType.Up); } else if (maximum < ThresholdDown) { log.Info($"Scaling logic: Value {maximum} is low, scaling {metric.ResourceName} down..."); action = new ScaleAction(metric.ResourceName, ScaleActionType.Down); } } // 4. If scaling is needed, call Scaler if (action != null) { var result = await context.CallFunctionAsync <int>(nameof(Scaler), action); log.Info($"Scaling logic: Scaled to {result} instances."); state.LastScalingActionTime = context.CurrentUtcDateTime; } context.ContinueAsNew(state); return(state); }
public static async Task <int> PeriodicTask( [OrchestrationTrigger] DurableOrchestrationContext ctx, TraceWriter log) { var timesRun = ctx.GetInput <int>(); timesRun++; if (!ctx.IsReplaying) { log.Info("starting the periodic task activity " + ctx.InstanceId + " " + timesRun); } await ctx.CallActivityAsync("A_PeriodicActivity", timesRun); var nextRun = ctx.CurrentUtcDateTime.AddSeconds(15); await ctx.CreateTimer(nextRun, CancellationToken.None); ctx.ContinueAsNew(timesRun); return(timesRun); }
public static async Task IotHubScaleOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log) { log.Info("IotHubScaleOrchestrator started"); // launch and wait on the "worker" function await context.CallActivityAsync(IotHubScaleWorkerName); // register a timer with the durable functions infrastructure to re-launch the orchestrator in the future DateTime wakeupTime = context.CurrentUtcDateTime.Add(TimeSpan.FromMinutes(JobFrequencyMinutes)); await context.CreateTimer(wakeupTime, CancellationToken.None); log.Info(String.Format("IotHubScaleOrchestrator done... tee'ing up next instance in {0} minutes.", JobFrequencyMinutes.ToString())); // end this 'instance' of the orchestrator and schedule another one to start based on the timer above context.ContinueAsNew(null); }
public async Task Run([OrchestrationTrigger] DurableOrchestrationContext context, ILogger log) { var teamModel = context.GetInput <TeamModel>(); if (!teamModel.Initialized) { await context.CallSubOrchestratorWithRetryAsync(nameof(InitializeOrchestrator), _options.AsRetryOptions(), teamModel); teamModel.Initialized = true; } try { var weeks = context.CurrentUtcDateTime.Date .Range(_options.PastWeeks, _options.FutureWeeks, _options.StartDayOfWeek); var weekTasks = weeks .Select(startDate => new WeekModel { StartDate = startDate, StoreId = teamModel.StoreId, TeamId = teamModel.TeamId }) .Select(weekModel => context.CallSubOrchestratorWithRetryAsync(nameof(WeekOrchestrator), _options.AsRetryOptions(), weekModel)); await Task.WhenAll(weekTasks); } catch (Exception ex) when(_options.ContinueOnError) { log.LogTeamError(ex, teamModel); } if (_options.FrequencySeconds < 0) { return; } var dueTime = context.CurrentUtcDateTime.AddSeconds(_options.FrequencySeconds); await context.CreateTimer(dueTime, CancellationToken.None); context.ContinueAsNew(teamModel); }
public static async Task Run( [OrchestrationTrigger] DurableOrchestrationContext counterContext, TraceWriter log) { int numberOfExecutions = 0; try { numberOfExecutions = counterContext.GetInput <int>(); log.Info($"********{counterContext.InstanceId}: Current counter state is {numberOfExecutions}. isReplaying: {counterContext.IsReplaying} Waiting for next operation.**************"); log.Info($"*********{counterContext.InstanceId}: Call activity ExistFile from {numberOfExecutions}*************"); var existsFile = await counterContext.CallActivityAsync <bool>("ExistFile", numberOfExecutions.ToString()); if (existsFile) { log.Info($"*********{counterContext.InstanceId}: EXISTS FILE {numberOfExecutions}.json *************"); log.Info($"*********{counterContext.InstanceId}: Call activity AddCRM from {numberOfExecutions}*************"); await counterContext.CallActivityAsync("AddCRM", numberOfExecutions.ToString()); log.Info($"*********Add element to queeue *************"); await counterContext.CallActivityAsync("AddQueueTrigger", numberOfExecutions.ToString()); log.Info($"*********END element to queeue *************"); } else { log.Info($"*********{counterContext.InstanceId}: NO EXIST FILE {numberOfExecutions}.json *************"); } log.Info($"*********Return {counterContext.InstanceId}: FINISH from {numberOfExecutions}*************"); } catch (Exception ex) { log.Error($"**********ERROR General execution: {numberOfExecutions} - {counterContext.IsReplaying} - {counterContext.InstanceId} *********", ex.InnerException != null ? ex.InnerException : ex); if (!counterContext.IsReplaying) { log.Info($"**********RETRY execution: {numberOfExecutions} - {counterContext.InstanceId} *********"); counterContext.ContinueAsNew(numberOfExecutions); } } }
public static async Task MakeTemplate( [OrchestrationTrigger] DurableOrchestrationContext context) { var list = context.GetInput <List <string> >() ?? new List <string>(); var value = await context.WaitForExternalEvent <string>(Consts.DurableEventNameAddToTemplate); if (value.StartsWith(Consts.FinishMakingTemplate)) { // 完成したリストをReplyトークンとともに返信Activityに渡す var token = value.Replace(Consts.FinishMakingTemplate + "_", string.Empty); await context.CallActivityAsync(nameof(SendTemplates), (token, list)); } else { // リストにセリフを追加しオーケストレーターを再実行 list.Add(value); context.ContinueAsNew(list); } }
public static async Task <int> PeriodicTask( [OrchestrationTrigger] DurableOrchestrationContext ctx, TraceWriter log) { var timesRun = ctx.GetInput <int>(); timesRun++; if (!ctx.IsReplaying) { log.Info($"Starting the PeriodicTask orchestrator {ctx.InstanceId}, {timesRun}"); } await ctx.CallActivityAsync(ActivityNames.PeriodicActivity, timesRun); var nextRun = ctx.CurrentUtcDateTime.AddSeconds(30); await ctx.CreateTimer(nextRun, CancellationToken.None); ctx.ContinueAsNew(timesRun); return(timesRun); }
public static async Task <UserData> Run([OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log) { var user = context.GetInput <UserData>() ?? new UserData(); var updateUserTask = context.WaitForExternalEvent <UserData>(UserEvents.UpdateUser); // Wait for external events var resultingEvent = await Task.WhenAny(updateUserTask); // Update User if (resultingEvent == updateUserTask) { user = updateUserTask.Result; log.Info($"Updated {updateUserTask.Result.FirstName}."); } context.ContinueAsNew(user); // the magic line return(user); }
public static async Task ProcessOrderSubOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context, ILogger log) { // Waiting... await context.CreateTimer(context.CurrentUtcDateTime.Add(ChargingPeriod), CancellationToken.None); var policy = context.GetInput <WhatIfDemoDbDataContext.Policy>(); // If the charging time interval is not expired yet if ((context.CurrentUtcDateTime - policy.dateCreated) < ChargingInterval) { // Sending a periodic billing email await context.CallActivityAsync <WhatIfDemoDbDataContext.Policy>(nameof(ChargeTheCustomer), policy); // Recursively calling ourselves context.ContinueAsNew(policy); } }
public static async Task Math([OrchestrationTrigger] DurableOrchestrationContext ctx) { int counterState = ctx.GetInput <int>(); string operation = await ctx.WaitForExternalEvent <string>("operation"); if (operation.Equals("add")) { counterState++; } else if (operation.Equals("subtract")) { counterState--; } else { throw new ArgumentException("Event data should be either 'add' or 'subtract'"); } ctx.ContinueAsNew(counterState); }
public static async Task <GameSession> RunOrchestrator( [OrchestrationTrigger] DurableOrchestrationContext context) { var notifications = new List <Notification>(); var gameState = context.GetInput <GameSession>(); if (gameState.Players.Count < 2) { var playerTwoId = await context.WaitForExternalEvent <int>(Constants.PlayerJoinEventTag); gameState.Players.Add(playerTwoId); gameState.StartGame(); await context.CallActivityAsync("SetupNotifications", gameState); notifications.Add(new Notification { Type = NotificationType.PlayerMessage, ToId = playerTwoId.ToString(), Message = "The game has started. You go first!" }); } else { // listen for events var timeoutCts = new CancellationTokenSource(); var turnTimeoutAt = context.CurrentUtcDateTime.AddSeconds(gameState.TurnTimeoutSec); var timeoutTask = context.CreateTimer(turnTimeoutAt, timeoutCts.Token); var turnEventTask = context.WaitForExternalEvent <PlayerTurn>(Constants.PlayerTurnEventTag); var forfeitEventTask = context.WaitForExternalEvent <int>(Constants.PlayerForfeitEventTag); var nextEvent = await Task.WhenAny(timeoutTask, turnEventTask, forfeitEventTask); if (!timeoutTask.IsCompleted) { // all pending timers must be complete or canceled before the function exits timeoutCts.Cancel(); } // handle events if (nextEvent == forfeitEventTask || nextEvent == timeoutTask) { // determine who forfeits var forfeitedPlayerId = nextEvent == forfeitEventTask ? forfeitEventTask.Result : gameState.CurrentTurnPlayer; // update game state gameState.Forfeit(forfeitedPlayerId); notifications.Add(new Notification { Type = NotificationType.GroupMessage, ToId = context.InstanceId, Message = $"Player {forfeitedPlayerId} forfeited." }); } else if (nextEvent == turnEventTask) { // update game state var turnData = turnEventTask.Result; if (turnData.PlayerId == gameState.CurrentTurnPlayer) { gameState.PlayTurn(turnData.Coordinates.X, turnData.Coordinates.Y); } } // determine if game ends if (gameState.RemainingMoves == 0) // draw { notifications.Add(new Notification { Type = NotificationType.GroupMessage, ToId = context.InstanceId, Message = "The match was a draw." }); } else if (gameState.Winner.HasValue) // win-loss { notifications.Add(new Notification { Type = NotificationType.GroupMessage, ToId = gameState.Winner.ToString(), Message = $"Player {gameState.Winner} has won." }); } } // persist game state await context.CallActivityAsync("PersistGameSession", gameState); // send notifications notifications.Add(new Notification { Type = NotificationType.StateUpdate, ToId = context.InstanceId, Message = JsonConvert.SerializeObject(gameState) }); if (gameState.Status != GameSessionStatus.Completed) { context.ContinueAsNew(gameState); } await context.CallActivityAsync("CleanupNotifications", gameState); return(gameState); }