3. Hello, world! - momoadept/FleetCommand GitHub Wiki

This tutorial will guide you through outputting "Hello, world!" in Space Engineers using AdeptOS. It will get you started with some basic concepts before we'd need to go deeper. I assume that you went through Getting Started guide and have an empty project set up for your script, we'll go from there.

Minification settings

I recommend using "Trim unused types" and "Lite (no renaming)" by default. No renaming will help understand stack traces if something goes wrong, and the minification amount is good enough.

Running the empty project

It's a good opportunity to see what AdeptOS does without any user code. Use MDK Deploy Script to build your script.

If you are getting errors at this stage, probably your project references are not configured correctly, see Getting Started guide

Get yourself a programmable block and plug your script in. After you're done, you should see AdeptOS performance output in the detailed info section of your PB. It should look something like this:

Also, the PB screen will start to display something.

Whenever you make changes to the script, re-deploy with MDK-SE and load the new version in the PB.

Creating a module

First, you need a module. It is the main building block of your code. Let's start by creating a new class using Utility Class template from the MDK-SE and name it HelloWorldModule.

partial class Program
{
    public class HelloWorldModule
    {

    }
}

Now we'll make it an AdeptOS module. We do it via implementing IModule interface (HelloWorldModule : IModule). At this point, and honestly every time you use AdeptOS types, I strongly recommend using your Visual Studio magic to generate not implemented members, since it is the best way to not miss anything. Let's do it and see what IModule is:

public class HelloWorldModule : IModule
{
    public string UniqueName { get; }
    public string Alias { get; }

    public void Bind(IBindingContext context)
    {
    }

    public void Run()
    {
    }

    public void OnSaving()
    {
    }
}

Now we are interested in Bind and Run methods, but I will go into more details on other stuff in later tutorials.

Bind() method

This is called by the framework once when the PB is loaded. It is the place where you do your Dependency Injection. If your module needs to use other modules, it will receive their instances here using the context object. Let's pick up a few built-in modules that we will need for this tutorial (actually these two are a part of almost every module):

ILog _log;
IGameContext _gameContext;

public void Bind(IBindingContext context)
{
    _log = context.RequireOne<ILog>();
    _gameContext = context.RequireOne<IGameContext>();
}

Now, in our module class, we have _log and _gameContext properties, that are in fact other modules, and we can use them. Similarly, other modules can use our module, if we were to give it an interface, but for now we don't need to bother.

Run() method

This is called by the framework once when the PB is loaded, but strictly after all the Bind() calls. Here you can be sure that all your dependencies are in place and can start doing your stuff. Hello, world! will go in here. This method parallels your public Program() constructor for vanilla scripting as an entry point. In general, a module is very similar to a vanilla script, and where you were to use Program() and Save(), you now use Run() and OnSaving(). This way framework can run multiple modules without them knowing about each other. But there is a difference:

Where is Tick()?

We don't have Tick(). Every module doesn't neccessarily need one, and those that do, may need different update frequencies. We achieve this by using AdeptOS Jobs, and create them in Run() method. We'll look into that in later tutorials. For now let's see how we can output something.

Bootstrapping

Before we do that, we need to let AdeptOS know that it should load our module. You do it by going to Program.cs and adding our module instance to Modules = new List<IModule>() definition:

Modules = new List<IModule>()
{
    new BlackBoxLogger(),
    new HelloWorldModule()
}

Now the framework will actually call methods on our module.

Where can "Hello, world!" go?

We'll go through two simplest options here. We will write in a log, and on an LCD panel.

Writing to a log

Previously, we have obtained a ILog _log object. AdeptOS has a built-in extensible log system. How would you debug your code otherwise? By default, the log goes to the PB main screen. Feel free to fiddle with font size on your own. Let's add some code to our Run():

public void Run()
{
    _log.Info("Hello, world!");
}

That's it. Now run this in your PB and you will see the text on the screen.

Writing on an LCD

Let's assume that you have built an LCD and named it [HelloWorld] Lcd whatever. To write there we need to obtain IMyTextPanel reference, after that it will go exactly as with vanilla scripting. But, since we are now in a module class, and not in Program, we cannot use Me and GridTerminalSystem properties! For that we injected IGameContext _context. It has Me and Grid for you to use.

There is a pre-built Tag type, let's use it together with _gameContext to discover our LCD. Add some code to Run() so it looks like this:

public void Run()
{
    _log.Info("Hello, world!");

    var lcdTag = new Tag("HelloWorld"); // This will search for [HelloWorld]
    var lcd = Tag
        .FindBlockByTag<IMyTextPanel>(lcdTag, _gameContext.Grid)
        .First();
}

Note that you can wave the .First() call if you want to output to every LCD that has the tag, but you would need to use foreach cycle to do that

Now lcd is your LCD reference. Note that if no block is discovered, .First() will throw, and the exception will be visible in the log. You can try this bad case on your own to get familiar to how the framework behaves when things go badly. For now let's use the LCD like this:

lcd.ContentType = ContentType.TEXT_AND_IMAGE;
lcd.WriteText("Hello, world!");

That's it. Now if you run it, and if your tag is set up correctly, you will see the text both on the PB screnn and on the LCD.

Conclusion

In the end, the code of your module should look like this:

partial class Program
{
    public class HelloWorldModule : IModule
    {
        public string UniqueName { get; }
        public string Alias { get; }

        ILog _log;
        IGameContext _gameContext;

        public void Bind(IBindingContext context)
        {
            _log = context.RequireOne<ILog>();
            _gameContext = context.RequireOne<IGameContext>();
        }

        public void Run()
        {
            _log.Info("Hello, world!");

            var lcdTag = new Tag("HelloWorld"); // This will search for [HelloWorld]
            var lcd = Tag
                .FindBlockByTag<IMyTextPanel>(lcdTag, _gameContext.Grid)
                .First();

            lcd.ContentType = ContentType.TEXT_AND_IMAGE;
            lcd.WriteText("Hello, world!");
        }

        public void OnSaving()
        {
        }
    }
}

It is my expectation that after this tutorial you can use AdeptOS as if you were writing a vanilla script. Thousands of lines of code that sit there and do nothing, isn't that cool? Well, maybe you will at least find the Tag class usefull. Please go on with the tutorials to see what AdeptOS brings to the table.

⚠️ **GitHub.com Fallback** ⚠️