Added
-
makeData— creates aMOGDataof a given size, filled with a given byte value (e.g.1024 0 makeDatafor 1024 zero bytes,1023 0xFF makeDatafor 1023 bytes all set to0xFF), without pushing each byte onto the stack first. Building a large buffer viarepeat { 0 } ->dataallocates oneMOGObjectper byte before conversion, which can exhaust memory on lower-RAM devices even though it works fine on more capable ones;makeDataavoids that transient peak entirely -
nano.send— sends an arbitrary string to the connected device:"TIME=15:45" nano.send. The device fires aSTUDIO_DID_SENDevent on the currently running program, with the received string available aseventData(a plainMOGString), letting a long-running program (forever do { ... }) react to free-form commands from Studio without needing to be stopped and relaunched. No built-in message format is imposed — parsing the string (e.g. aNAME=valueconvention) is entirely up to the receiving script -
->vars,->safeVars,->params— ported with 100% functional parity from the desktop MOGWAI engine.->varsextracts values from a record or the stack straight into matching local variables, with no type checking beyond raising an error if the stack doesn't have enough elements.->safeVarsdoes the same but also validates each value's type against a declared record, and is whatto ... with [...] douses automatically for typed function parameters.->paramsvalidates a named-parameter record (with optional default values) against a declared shape, raising an error if a required parameter is missing or mistyped, and silently ignoring extras -
Bitwise operators on
.number—&(AND),|(OR),^(XOR),~(invert),<</>>(shift left/right). Note the context-sensitive parsing of&:&Aright before a name is still the existing reference sigil, whileX Y &after two numbers already on the stack is the bitwise AND primitive — the two are distinguished by position, not a separate symbol. No.binary/B:type was introduced for this — these operators work directly on regular numbers, which was enough for the intended use cases (e.g. manipulating individual pixels in a display frame buffer) without the overhead of a whole new type family -
floor,mod— standard floor and modulo, useful alongside the new bitwise operators for address/offset arithmetic (e.g. computing a byte offset and bit position from pixel coordinates) -
Engine.Idle()— a cooperative yield point called fromMOGCode.Execute()on every executed item (both inside and outside loops), callingThread.Sleep(0)every 10 iterations. The nanoCLR schedules threads cooperatively rather than preemptively (see the nanoFramework thread execution docs) — a tight loop that never yields can starve other threads of CPU time, including the device's own network thread. This was observed concretely: two empty nested loops running for more than ~10 seconds would prevent the TCP thread from ever getting a chance to run, eventually causing MOGWAI NANO Studio to lose the connection even though the device itself was working correctly the whole time -
SSD1306 OLED display support — a dedicated, native (non-RPN) primitive family wrapping the
nanoFramework.Iot.Device.Ssd13xxbinding, added after measuring that dense per-pixel drawing in pure RPN (deep-cloning/re-parsing overhead per function call) was multiple orders of magnitude too slow for practical use. Fixed to 128x64 resolution over I2C Fast Mode for now — MOGWAI NANO officially supports this specific display type rather than exposing a generic OLED abstraction. Devices are managed as a single global instance (no name-based multi-display support yet), covering:ssd1306.init(bus, address),ssd1306.close,ssd1306.clear,ssd1306.printString/ssd1306.drawString(x, y, text, size, center) —printStringuses character-grid coordinates (like a text console,x=0 y=1meaning the start of the second text line), whiledrawStringuses pixel coordinates for precise, free-form placement,ssd1306.refresh,ssd1306.drawPixel,ssd1306.drawHorizontalLine/ssd1306.drawVerticalLine,ssd1306.drawRectangle(outline, hand-composed from four calls to the horizontal/vertical line primitives since the underlying binding has no dedicated rectangle-outline method),ssd1306.drawFilledRectangle, andssd1306.drawBitmap(drawing a rawMOGDatabuffer as a 1-bit-per-pixel image). A dedicatedMW.52xerror range covers display-specific failures (already open, not open, initialization failure, general operation failure) -
The device now declares
'SSD1306'as an additional skill alongside'GPIO'and'I2C', queryable the same way ('SSD1306' hasSkill) -
The new
ssd1306.*primitives are recognized and syntax-highlighted by the MOGWAI VS Code extension, following the same zero-modification stub pattern as the other NANO-specific primitives -
Lazy parsing /
frugalMode— a major memory management addition, refined over the course of extensive testing into a clean, unified mechanism. Previously, running a script parsed its entire source into a full object tree upfront, and every executed block was deep-cloned (recursively) before evaluation — necessary to protect against in-place mutation (e.g. a list built with+/AddIteminside a loop must start fresh on every iteration, or3 { (1 2 3) 4 + ? } REPEATwould print a list that keeps growing across iterations instead of the same(1 2 3 4)three times). On a device with only ~54KB of RAM, this meant a script with several function definitions could consume tens of kilobytes just to parse and clone, well before running any real logic — a script of only ~3KB of source text was measured consuming over 23KB during parsing alone.MOGCode/MOGFunctionnow support two modes, toggled withtrue/falsemogwai.frugalMode, switching takes effect on the very next execution of any block regardless of which mode it was originally parsed under:- cool (the default) — parse a block once on first use, keep the resulting object tree cached, and protect against mutation between repeated executions (loop iterations, repeated calls) via deep-cloning. Fast, but memory cost is proportional to total program size and never recovered until the block itself is discarded.
- frugal — parse a block lazily on first use, discard the parsed objects immediately after each execution, and protect against mutation by simply re-parsing from source on the next call rather than cloning — the untouched source text guarantees a fresh, uncorrupted object tree every time. Flat, stable memory footprint regardless of program size or loop iteration count, at the cost of repeating the parsing work on every single call.
Measured on a real 128x64 OLED display test (nested loops drawing 121 pixels, each via a function call): 11s in cool mode vs 23s in frugal mode, in a Release build — roughly a 2x speed trade-off for a flat memory profile. (Note: this gap nearly disappears in a Debug build, where cloning and re-parsing end up costing about the same — always benchmark this trade-off in Release.) Cool mode combined with lazy parsing (parsing only what's actually invoked, rather than upfront) already recovers most of the original memory problem for typical scripts; frugal mode remains the right choice for very long-running programs with many repeated calls on memory-constrained devices.
Updated
mogwai.info(device-side) andnano.info(Studio-side) records now also include askills:key, listing the same skills queryable viaskills/hasSkill— lets you check a device's capabilities from a single info call, without a separate query- RPN stack storage: replaced the
ArrayList-backed execution stack with a dedicatedMOGStackclass using a plainMOGObject[]array with manual growth (doubling capacity as needed, starting small to keep the per-scope memory footprint low). Since every value on the stack is already aMOGObjectreference, this removesArrayList's generic overhead entirely with no boxing trade-off — measured as a very significant speedup on stack-heavy operations (e.g. building a largeMOGDatabuffer by pushing hundreds of values withrepeatbefore converting with->data) - I2C write primitives confirmed working with large multi-byte buffers in a single transaction (not just single bytes or short sequences) — validated by initializing and clearing a full 128x64 OLED display (SSD1306) frame buffer (1024 bytes) in one
i2c.register.writecall - I2C write primitives now accept a
MOGDatabuffer by reference (&myBuffer) rather than only by value, avoiding an unnecessary copy of the buffer on every call — most useful for a large, frequently-updated buffer like a display frame buffer FOR/FORSTEPno longer allocate a newMOGNumberfor the loop counter on every iteration — the same object is now reused and its value updated in place. This changes loop variable semantics slightly: a reference to the loop variable (&i) always reflects its current value, even after the loop has moved on — code that needs to preserve a snapshot of the value from a specific iteration (e.g. collecting values into a list) must explicitly copy it (i -> 'snapshot') rather than storing a reference to the loop variable itself- The outgoing message queue is now split into a priority lane (
PROGRAM.DID.START,PROGRAM.DID.STOP,STATE.GET,PONG, and other control messages) and the regular capped lane (console.print/debug.writeoutput). The priority lane is never capped and is always drained first, so a program producing a lot of console output can no longer delay or push out a control message that MOGWAI NANO Studio is actively waiting on
Fixed
- If MOGWAI NANO Studio was killed abruptly while a program on the device kept sending console/debug output, a new connection attempt would silently hang for up to 30 seconds (the idle timeout) before succeeding — the failed writes on the old, dead connection were detected but never actually signaled anywhere, leaving the device's TCP accept loop stuck on the stale connection. A shared flag now lets a failed write immediately unblock the read loop, so a fresh reconnection succeeds right away instead of waiting out the timeout
- Three more occurrences of the static-initialization-order issue already described for
EvalResult.NoError/theErrorclass (see below) were found and fixed:EvalResult.Erroritself (via a C# 9initaccessor combined with a field initializer — replaced with a plain constructor-assigned property), andMogwaiNanoEngine.LastResult/LastError(both{ get; set; } = ...property initializers referencing another class's static member — replaced with the same lazy-initialization pattern). Any of these beingnullat the wrong moment could crash the device's execution dispatcher with a hard-to-diagnoseCLR_E_WRONG_TYPE/NullReferenceExceptionpair Error.FatalErrorwas declared but never actually registered inError's lazy initialization — a missing line meant it silently stayednullforever. Since it's used byMOGCode's catch-all safety net for unexpected exceptions during script execution, this masked the real underlying error message every time that safety net triggered, itself crashing on the samenull-related symptom instead of reporting what actually went wrong- Fixed the operand order in
mod:y 8 modwas computing8 mod yinstead ofy mod 8, which happened to go unnoticed on small test values before producing an out-of-range result on a real calculation (30 mod 8returning8, an impossible modulo-8 result) MOGData.Clone()didn't actually copy its underlying byte array — it shared the same array reference with the original, so mutating one throughsetwould silently corrupt the other. Now copies the array on clone- I2C register writes were briefly split into two separate
WriteByte/Writecalls (as a memory optimization, to avoid allocating a combined buffer on every call) — this broke the repeated-start requirement some I2C devices rely on to treat the register address and the following data as a single logical write, causing an OLED display (SSD1306) to silently accept every command over I2C without ever actually updating its output. Reverted to a single combined write; lazy parsing/frugalMode(see above) turned out to be the right way to address the original memory concern instead FORwas missing areturnon its early argument-count failure path — if the stack didn't have enough elements, it constructed an error result but then fell straight through to the type-check line anyway, indexing into an already-empty array and risking a lower-level crash instead of cleanly reporting the error- MOGWAI NANO Studio was treating an ordinary TCP read timeout (used to periodically re-check the connection, no message received in the configured window) as a hard disconnection — reported as
Unable to read data from the transport connection. A program that stays quiet for a while (heavy computation with no console output in between) would trip this every time the timeout elapsed, even though the connection and the device were both fine. A plain read timeout is no longer treated as a disconnect
Known Limitations
- Occasional network disconnections between MOGWAI NANO Studio and a device are expected over WiFi — they can originate on either side (device WiFi hiccups, or a transient network interruption on the PC) and the exact interval is not constant. Reconnection is fast and doesn't require restarting Studio.