Skip to content

Commit 40d8de3

Browse files
committed
Improved some documentation for the coroutines. Added a stopwatch library.
1 parent a65b78b commit 40d8de3

22 files changed

Lines changed: 1394 additions & 4 deletions

File tree

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,88 @@
1-
R e a d m e
2-
-----------
1+
Coroutines Demo - Script Mixins for Space Engineers
2+
===================================================
3+
4+
This demo showcases the Coroutines mixin, which brings async-style programming to Space Engineers.
5+
6+
WHAT ARE COROUTINES?
7+
--------------------
8+
Coroutines let you write sequential code that spans multiple game ticks without blocking. Instead
9+
of complex state machines, you can write natural "do this, wait, then do that" logic using yield return.
10+
11+
WHAT THIS DEMO DOES:
12+
--------------------
13+
1. Runs a continuous coroutine that prints "Hello, World!" every update cycle
14+
2. Shows how to wait for other coroutines to complete (sequence -> subsequence pattern)
15+
3. Demonstrates cancellation tokens for stopping long-running tasks
16+
4. Examples of condition-based waiting
17+
18+
HOW TO USE THIS DEMO:
19+
---------------------
20+
- The script runs automatically - you'll see "Hello, World!" echoed continuously
21+
- Run with argument "raise" to start a sequence that waits for a command
22+
- Run with argument "command" while sequence is waiting to fulfill the condition
23+
- Run with argument "cancel" to stop the main Hello World loop
24+
25+
KEY CONCEPTS DEMONSTRATED:
26+
--------------------------
27+
1. BASIC COROUTINE: MainCr() - Runs continuously until cancelled
28+
2. SEQUENTIAL EXECUTION: SequenceCr() waits for SubsequenceCr() to complete
29+
3. CONDITION WAITING: SubsequenceCr() waits until _didRaiseCommand becomes true
30+
4. CANCELLATION: The main loop can be cancelled, cleanly stopping execution
31+
32+
COROUTINE STRUCTURE:
33+
--------------------
34+
Coroutines are methods that return IEnumerator<When> and use yield return:
335

4-
In this file you can include any instructions or other comments you want to have injected onto the
5-
top of your final script. You can safely delete this file if you do not want any such comments.
36+
public IEnumerator<When> MyCoroutine()
37+
{
38+
Echo("Starting...");
39+
yield return When.NextUpdate(); // Wait one tick
40+
Echo("After one tick");
41+
yield return When.TimePassed(1000); // Wait 1 second
42+
Echo("Done!");
43+
}
44+
45+
COMMON WAIT CONDITIONS:
46+
-----------------------
47+
- When.NextUpdate() - Wait for next update (10 ticks, ~167ms)
48+
- When.NextUpdate1() - Wait one tick (16.67ms) - use sparingly!
49+
- When.NextUpdate100() - Wait 100 ticks (~1.67s) - for slow polling
50+
- When.TimePassed(ms) - Wait specified milliseconds
51+
- When.True(() => condition) - Wait until condition becomes true
52+
- When.Completed(id) - Wait for another coroutine to finish
53+
54+
STARTING COROUTINES:
55+
--------------------
56+
Use Coroutines.Run() to start a coroutine:
57+
58+
Coroutines.Run(MyCoroutine());
59+
60+
Returns a ulong ID that can be used with When.Completed() to wait for it.
61+
62+
REQUIRED: CALL Coroutines.Main() IN YOUR Main() METHOD:
63+
--------------------------------------------------------
64+
public void Main(string argument, UpdateType updateSource)
65+
{
66+
Coroutines.Main(argument, updateSource); // Process all coroutines
67+
// Your other code here
68+
}
69+
70+
WHY USE COROUTINES?
71+
-------------------
72+
✓ Write sequential logic instead of complex state machines
73+
✓ Spread heavy work across multiple ticks to avoid script timeout
74+
✓ Wait for conditions, timers, or other coroutines naturally
75+
✓ Run multiple tasks in parallel easily
76+
✓ Cancel long-running operations cleanly
77+
78+
PERFORMANCE NOTES:
79+
------------------
80+
- Use Update10 (default) for most cases - it's efficient
81+
- Only use Update1 when precise timing is critical
82+
- Keep condition checks lightweight - they run every check cycle
83+
- Monitor Coroutines.Count if spawning many dynamic coroutines
84+
85+
LEARN MORE:
86+
-----------
87+
See the full readme.md in the Coroutines source folder for comprehensive documentation,
88+
including ForEach for processing collections, advanced patterns, and a complete airlock example.

libraries/Mal.MdkScriptMixin.Coroutines/Mal.MdkScriptMixin.Coroutines/Mal.MdkScriptMixin.Coroutines.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<Import_RootNamespace>IngameScript</Import_RootNamespace>
1010
</PropertyGroup>
1111
<ItemGroup>
12+
<Content Include="$(MSBuildThisFileDirectory)readme.md" />
1213
<Content Include="$(MSBuildThisFileDirectory)_authors" />
1314
<Content Include="$(MSBuildThisFileDirectory)_version" />
1415
<Content Include="$(MSBuildThisFileDirectory)_releasenotes" />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Stopwatch Demo - Simple Timing for Space Engineers
2+
===================================================
3+
4+
This demo shows you how to use the Stopwatch to measure time across script runs.
5+
6+
IMPORTANT: This stopwatch measures GAME TICKS, not execution time within a single run.
7+
It's useful for timing things that happen over multiple script executions.
8+
9+
BASIC USAGE:
10+
------------
11+
1. Create a stopwatch: new Stopwatch(this)
12+
2. Start it: stopwatch.Start()
13+
3. Stop it: stopwatch.Stop()
14+
4. Read time: stopwatch.Elapsed
15+
16+
WHAT THIS DEMO DOES:
17+
--------------------
18+
- Creates one stopwatch that you control with commands
19+
- Shows the elapsed time (in game ticks) and whether it's running
20+
- Demonstrates measuring time between script runs
21+
22+
TRY THESE COMMANDS:
23+
-------------------
24+
start - Start the stopwatch (it will count game time from now)
25+
stop - Stop the stopwatch (time is preserved)
26+
reset - Reset to zero
27+
28+
EXAMPLE OUTPUT:
29+
---------------
30+
=== STOPWATCH DEMO ===
31+
32+
Time: 00:12.345
33+
Running: True
34+
35+
Commands: start, stop, reset
36+
37+
WHAT CAN YOU USE THIS FOR?
38+
---------------------------
39+
✓ Measure how long a task takes across multiple script runs
40+
✓ Create timers (e.g., "do something every 5 seconds")
41+
✓ Track how long your script has been running
42+
✓ Implement cooldowns
43+
44+
✗ NOT for measuring performance within a single script run
45+
✗ NOT for finding bottlenecks in your code
46+
✗ NOT for sub-tick precision timing
47+
48+
THE CODE:
49+
---------
50+
The demo shows manual control of a stopwatch:
51+
_stopwatch.Start(); // Start counting game time
52+
_stopwatch.Stop(); // Stop counting
53+
_stopwatch.Reset(); // Reset to zero
54+
55+
TIME FORMAT:
56+
------------
57+
The Elapsed property gives you a TimeSpan:
58+
- stopwatch.Elapsed.TotalSeconds // 12.3
59+
- stopwatch.Elapsed.ToString("mm\\:ss") // "00:12"
60+
61+
Resolution: ~16.67ms (one game tick at 60 ticks/second)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netframework48</TargetFramework>
4+
<RootNamespace>IngameScript</RootNamespace>
5+
<LangVersion>6</LangVersion>
6+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
7+
<Configurations>Release;Debug</Configurations>
8+
<Platforms>x64</Platforms>
9+
<PlatformTarget>x64</PlatformTarget>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<PackageReference Include="Mal.Mdk2.PbAnalyzers" Version="2.1.16">
13+
<PrivateAssets>all</PrivateAssets>
14+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
15+
</PackageReference>
16+
<PackageReference Include="Mal.Mdk2.PbPackager" Version="2.1.15">
17+
<PrivateAssets>all</PrivateAssets>
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
</PackageReference>
20+
<PackageReference Include="Mal.Mdk2.References" Version="2.2.7"/>
21+
</ItemGroup>
22+
<ItemGroup>
23+
<None Remove="Instructions.readme"/>
24+
<AdditionalFiles Include="Instructions.readme"/>
25+
<AdditionalFiles Include="thumb.png"/>
26+
</ItemGroup>
27+
<Import Project="..\Mal.MdkScriptMixin.Stopwatch\Mal.MdkScriptMixin.Stopwatch.projitems" Label="Shared" />
28+
</Project>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Sandbox.Game.EntityComponents;
2+
using Sandbox.ModAPI.Ingame;
3+
using Sandbox.ModAPI.Interfaces;
4+
using SpaceEngineers.Game.ModAPI.Ingame;
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using System.Linq;
10+
using System.Text;
11+
using VRage;
12+
using VRage.Collections;
13+
using VRage.Game;
14+
using VRage.Game.Components;
15+
using VRage.Game.GUI.TextPanel;
16+
using VRage.Game.ModAPI.Ingame;
17+
using VRage.Game.ModAPI.Ingame.Utilities;
18+
using VRage.Game.ObjectBuilders.Definitions;
19+
using VRageMath;
20+
21+
namespace IngameScript
22+
{
23+
public partial class Program : MyGridProgram
24+
{
25+
// Simple demonstration of the Stopwatch mixin
26+
// NOTE: Stopwatch measures GAME TIME (ticks), not execution time!
27+
28+
Stopwatch _stopwatch;
29+
30+
public Program()
31+
{
32+
// Create a stopwatch - it starts stopped at zero
33+
_stopwatch = new Stopwatch(this);
34+
35+
// Run every 100 ticks so we can see the time change
36+
Runtime.UpdateFrequency = UpdateFrequency.Update100;
37+
}
38+
39+
public void Main(string argument, UpdateType updateSource)
40+
{
41+
// Control the stopwatch with simple commands
42+
if (argument == "start")
43+
_stopwatch.Start();
44+
else if (argument == "stop")
45+
_stopwatch.Stop();
46+
else if (argument == "reset")
47+
_stopwatch.Reset();
48+
49+
// Show the current state
50+
Echo("=== STOPWATCH DEMO ===\n");
51+
Echo($"Game Time Elapsed: {_stopwatch.Elapsed:mm\\:ss\\.fff}");
52+
Echo($"Ticks Elapsed: {_stopwatch.ElapsedTicks}");
53+
Echo($"Running: {_stopwatch.IsRunning}\n");
54+
Echo("Commands: start, stop, reset\n");
55+
Echo("NOTE: This measures game time,");
56+
Echo("not execution time within a script run!");
57+
}
58+
}
59+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
; This file is project specific and should be checked in to source control.
2+
3+
[mdk]
4+
; This is a programmable block script project.
5+
; You should not change this.
6+
type=programmableblock
7+
8+
; Toggle trace (on|off) (verbose output)
9+
trace=off
10+
11+
; What type of minification to use (none|trim|stripcomments|lite|full)
12+
; none: No minification
13+
; trim: Removes unused types (NOT members).
14+
; stripcomments: trim + removes comments.
15+
; lite: stripcomments + removes leading/trailing whitespace.
16+
; full: lite + renames identifiers to shorter names.
17+
minify=none
18+
19+
; A list of files and folder to ignore when creating the script.
20+
; This is a comma separated list of glob patterns.
21+
; See https://code.visualstudio.com/docs/editor/glob-patterns
22+
ignores=obj/**/*,MDK/**/*,**/*.debug.cs
23+
24+
; A list of allowed namespaces. All ingame script code should be within one of these namespaces.
25+
; This is a comma separated list.
26+
;
27+
; WARNING: The programmable block strips all namespaces from the final script.
28+
; If you have two types with the same name in different namespaces, there will be a conflict.
29+
; It's recommended to keep all your script code in a single namespace to avoid issues.
30+
namespaces=IngameScript
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
; This file is _local_ to your machine and should not be checked in to source control.
2+
3+
[mdk]
4+
; Where to output the script to (auto|specific path)
5+
output=auto
6+
; Override the default binary path (auto|specific path)
7+
binarypath=auto
221 KB
Loading
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net48</TargetFramework>
5+
<SpaceEngineersBinCopyLocal>true</SpaceEngineersBinCopyLocal>
6+
<Configurations>Release;Debug</Configurations>
7+
<Platforms>x64</Platforms>
8+
<PlatformTarget>x64</PlatformTarget>
9+
<RootNamespace>Mal.MdkScriptMixin.Stopwatch.Tests</RootNamespace>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="FakeItEasy" Version="9.0.0" />
14+
<PackageReference Include="FakeItEasy.Analyzer.CSharp" Version="6.1.1">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
18+
<PackageReference Include="Mal.Mdk2.References" Version="2.2.7">
19+
<PrivateAssets>all</PrivateAssets>
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
</PackageReference>
22+
<PackageReference Include="NUnit" Version="4.4.0" />
23+
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<ProjectReference Include="..\Mal.MdkScriptMixin.Stopwatch.Demo\Mal.MdkScriptMixin.Stopwatch.Demo.csproj" />
28+
</ItemGroup>
29+
30+
</Project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Getting Started
2+
3+
Before doing anything:
4+
- Add a reference to the script project you want to test.
5+
- Make sure your Program class is `public`.
6+
7+
This test project will neither compile nor run properly if you don't do this.
8+
9+
Take a look in Tests/InstancingTests.cs for some simple examples of how to write tests.

0 commit comments

Comments
 (0)