Modular Computers the manual GitHub

Writing programs

Every mcos program is a Lua script. You write one with edit, run it by its path, and stop it with Ctrl+C. This page is about the one thing that differs most from how Lua programs are written for other Minecraft computers: you do not yield, and you do not need an event loop. Your program is a plain program. When it has nothing to do, it waits.

Waiting is how a program idles

A call that has to wait blocks your program until it can finish. The computer runs other things meanwhile and wakes yours when it is time. There are three ways to wait, and they cover everything:

To wait for Use Example
Some time sys.sleep(seconds) sys.sleep(0.5)
One device or file a read that blocks sensor:read("l") gives the next line, when there is one
Several things at once, or a change sys.poll(fds, seconds) wait for a card to change, a keypress and a timeout together

A device’s file is the way into it, and an event is a line you read from it, so waiting for an event is just reading. See /dev for the files, the sys library for sys.sleep and sys.poll, and each device’s page for what its file says.

A loop that never ends is fine

This is a whole program. It blinks a lamp forever:

-- Blink whatever is on the redstone card's north face: on half a second, off half a second, forever.
--
-- There is no event loop and nothing to yield: this is a plain loop. sys.sleep waits (the
-- computer does other work meanwhile) and the loop goes on until you stop it with Ctrl+C.
local sys = require("sys")

local FACE = "north"  -- any face: north, south, east, west, up, down (or front, back, left, right)
local HALF = 0.5      -- seconds on, and seconds off

-- A line written to the card's file is "face strength" and takes effect at once.
local card = io.open("/dev/redstone0", "w")

local function set(strength)
  card:write(FACE, " ", strength, "\n")
  card:flush()
end

while true do
  set(15)
  sys.sleep(HALF)
  set(0)
  sys.sleep(HALF)
end

Download redstone-blink.lua

Put it in a file, and with a redstone card in your computer and a lamp against that face of the case, run it:

edit /home/blink.lua
/home/blink.lua

What to notice:

Even a loop that never waits is not an error. The computer gives your program a fixed slice of work at a time, freezes it where it is when the slice is spent, and carries on with the server; your program is thawed in its next slice, exactly where it stopped. It does mean that a loop that does not wait uses all the time it is given, so wait when there is nothing to do.

Waiting for a change

A redstone card’s file also tells you when something changed. sys.poll waits for that, so this program uses no time at all while the signal is steady:

-- Copy the signal coming in on the west face out of the east face, and keep doing it.
--
-- The card's file says "face level powered" for each face: the level of the signal there, and how
-- much of it is this card's own. sys.poll waits (it blocks, it does not yield) until the card
-- reports a change, so the program uses no time while nothing happens.
local sys = require("sys")

local FROM = "west"
local TO = "east"

local reader = io.open("/dev/redstone0")
local writer = io.open("/dev/redstone0", "w")
local watch = {[sys.fileno(reader)] = {PRI = true}}  -- PRI: the device's state changed

-- Read the whole file from its start, and pick out one face's level.
local function level(face)
  reader:seek("set", 0)
  return tonumber(reader:read("a"):match(face .. " (%d+)")) or 0
end

local last = -1
while true do
  local now = level(FROM)
  if now ~= last then
    writer:write(TO, " ", now, "\n")
    writer:flush()
    last = now
  end
  sys.poll(watch)  -- block until the card changes
end

Download redstone-mirror.lua

Power the west face of the case and the east face follows it. The PRI event means “the device’s state changed”, which is how a redstone file says an input moved.

Waiting for an event

A sensor’s file has no state to read, only events: one line each. Reading it blocks until there is a line. This program sounds an alarm for each thing the motion sensor sees:

-- Sound an alarm (a redstone signal on the north face) whenever the motion sensor sees something move.
--
-- Reading a sensor's file blocks until it has an event, so the loop below is a plain while loop:
-- the program sleeps in read() until something moves, with no event.pull and no yield.
local sys = require("sys")

local ALARM = "north"
local PULSE = 1  -- seconds the alarm stays on

local motion = io.open("/dev/motion0")
local alarm = io.open("/dev/redstone0", "w")

local function set(strength)
  alarm:write(ALARM, " ", strength, "\n")
  alarm:flush()
end

while true do
  local line = motion:read("l")  -- blocks until the sensor has something to say
  local mob, x, y, z = line:match("^(%S+) (%S+) (%S+) (%S+)$")
  if mob then  -- (a line "lost n" says n events were dropped: nothing to sound for)
    print(mob .. " at " .. x .. " " .. y .. " " .. z)
    set(15)
    sys.sleep(PULSE)
    set(0)
  end
end

Download motion-alarm.lua

motion:read("l") is the program’s whole event loop. Events that arrive while the alarm sleeps wait in the sensor for the next read; a sensor that fills up reports how many it dropped, which is the lost line the program skips.

Doing two things at once

Two programs can run at once. End a command with & to start it in the background, list the running ones with ps, and stop one with kill:

/home/blink.lua &
ps

Inside one program, sys.poll waits on several descriptors at once, and says which of them is ready, so one loop can serve a sensor and the keyboard together.

If you know OpenComputers

In OpenComputers In mcos
event.pull() in a loop A blocking read on the device’s file, or sys.poll
os.sleep(t) sys.sleep(t)
A program that does not yield for a while is killed No such limit: a busy program is frozen and thawed
component.redstone.setOutput(sides.north, 15) redstone:write("north 15\n") on /dev/redstone0
sides.north the string "north": faces are strings
Components found by address Devices found by their names

The tutorials use these programs step by step.