Table of Contents

Async scripts

This C# Intermediate tutorial covers the usage of asynchronous scripts or async scripts.

Explanation

Up until this point every tutorial has been using sync scripts. That means that those scripts are executed right after each other. If one particular sync script would take 1 second to complete, our game would freeze that 1 second, until the update loop is complete. All of the previously made Sync scripts can be made into an Async script.

With Async scripts we can perform heavy duty operations or reach out to an api without it freezing our application. A game can be made entirely with either Sync or Async scripts, or a combination of them both.

Retrieving data from a web api

A common use case for async scripts is retrieving data from a web API. Depending on the speed of the API and the amount of data to be retrieved, this can take up to somewhere between 20 milliseconds and 2 seconds.

// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Stride.Core.Mathematics;
using Stride.Engine;

namespace CSharpIntermediate.Code
{
    public class AsyncWebApi : AsyncScript
    {
        private List<OpenCollectiveEvent> openCollectiveEvents;

        public override async Task Execute()
        {
            openCollectiveEvents = new List<OpenCollectiveEvent>();

            while (Game.IsRunning)
            {
                int drawX = 500, drawY = 600;
                DebugText.Print($"Press A to get Api data from https://opencollective.com/stride3d", new Int2(drawX, drawY));

                if (Input.IsKeyPressed(Stride.Input.Keys.G))
                {
                    await RetrieveStrideRepos();
                    await Script.NextFrame();
                }

                foreach (var openCollectiveEvent in openCollectiveEvents)
                {
                    drawY += 20;
                    DebugText.Print(openCollectiveEvent.Name, new Int2(drawX, drawY));
                }

                // We have to await the next frame. If we don't do this, our game will be stuck in an infinite loop
                await Script.NextFrame();
            }
        }

        private async Task RetrieveStrideRepos()
        {
            // We can use an HttpClient to make requests to web api's
            var client = new HttpClient();
            HttpResponseMessage response = await client.GetAsync("https://opencollective.com/stride3d/events.json?limit=4");

            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                // We store the contents of the response in a string
                string responseContent = await response.Content.ReadAsStringAsync();

                // We deserialze the string into an object
                openCollectiveEvents = JsonSerializer.Deserialize<List<OpenCollectiveEvent>>(responseContent);
            }
        }

        public class OpenCollectiveEvent
        {
            public string Name { get; set; }

            public string StartsAt { get; set; }
        }
    }
}

Async Collision trigger

In a previous tutorial we made a collision trigger script that would notify the user once an object is passing through it. We can make a similar script using Async script.

// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System.Threading.Tasks;
using Stride.Engine;
using Stride.Physics;
using Stride.Rendering;

namespace CSharpIntermediate.Code
{
    public class AsyncCollisionTriggerDemo : AsyncScript
    {
        private Material yellowMaterial;
        private Material redMaterial;

        public override async Task Execute()
        {
            // Store the collider component
            var staticCollider = Entity.Get<StaticColliderComponent>();
  
            //Preload some materials
            yellowMaterial = Content.Load<Material>("Materials/Yellow");

            while (Game.IsRunning)
            {
                // Wait for an entity to collide with the trigger
                var collision = await staticCollider.NewCollision();
                var ballCollider = staticCollider == collision.ColliderA ? collision.ColliderB : collision.ColliderA;

                // Store current material
                var modelComponent = ballCollider.Entity.Get<ModelComponent>();
                var originalMaterial = modelComponent.Materials[0];

                // Change the material on the entity
                modelComponent.Materials[0] = yellowMaterial;

                // Wait for the entity to exit the trigger
                await staticCollider.CollisionEnded();

                // Alternative
                // await collision.Ended(); //This checks for the end of any collision on the actual collision object

                // Change the material back to the original one
                modelComponent.Materials[0] = originalMaterial;
            }
        }

        public override void Cancel()
        {
            Content.Unload(yellowMaterial);
            Content.Unload(redMaterial);
        }
    }
}