March 19, 2025

0.6.6: Memory Management Improvements

This new milestone brings many low-level improvements to Red's memory management and garbage collecting. Most of those are long-planned additions needed to complete the internal memory model and make it robust enough for the future stable Red v1.0.

First, here is a simplified overview of the Red memory model (existing parts in green color, new parts in orange, non-Red parts in blue):


All Red values are stored in series. Some Red values require one or more buffers to hold their content. The values can never reference a buffer directly, but only through a node reference, to enable relocation when expanding the series buffer or when moving it around during compaction by the GC.


Now let's dive into the hairy details!


External resources GC

The Red/View engine backends rely on external resources provided by the OS. Among those resources, some are linked to face! or font! object and require special care when those objects are not reachable anymore. So far, our GC (Garbage Collector) was not able to release such resources (images bitmap buffers and fonts handles), as unreachable Red aggregate values are seeing as simple series during the sweeping GC stage. In order to improve that, we have added an external resources manager, that will track and free unused resources, allowing now unrestricted images and fonts usage!


Accurate GC

The Red GC relies on allocated memory walking and native stack scanning to identify live Red values. Scanning the native stack can be challenging. The scanner used so far a conservative approach, which is simpler, but can lead to corruptions or crashes in rare cases (e.g. a floating point number being mistaken for a series or node pointer). Moreover, such approach precluded from having a nodes frame GC, as there was no way to accurately identify node pointers on the stack. This is now solved. The plan was always to make it precise when getting closer to a Red v1.0 and that's what we did in this release.

In order to achieve that, several key additions were made:

➤ Frame records hints: the R/S compiler now generates a map of hints for arguments and local variables which are series/nodes pointers, using bit arrays stored in the .data segment and retrieved during scanning. In order to match a call frame on stack to the right bitmap array, an offset is now pushed on stack by each function call as part of their prolog sequence. Only the stack slots corresponding to 1's in the bit array are analyzed further to identify their origin series/node frame, then marked and stored in an list for the collector to later update them if needed. The bit arrays are compressed using our CRUSH algorithm implementation, so that, e.g. for the GUI console executable, all bit arrays add only about 3472 extra bytes to the final executable.

➤ Variadic hints (typed vs untyped): for variadic functions, the bit array is dynamically created. If the typed mode is used, an accurate bitmap is produced. If the generic untyped variadic mode is used, all the arguments stack slots will be marked for processing. This could, in theory, create false positives, but in practice, in Red's runtime code, all such cases are safe, referencing only Red values.

➤ Optimized pointers identification performance: each extracted pointer from the stack needs to be confirmed to be a valid series or a node pointer. Such checking is now achieved using cached sorted lists and binary search, ensuring vastly faster operation.

➤ Optimized frame walking by skipping non-Red frames: the stack scanning is done by jumping between call frames, relying on the saved frame pointer in each frame to chain the frames. However, when R/S callback functions are invoked by external (mostly OS) code, those external frames should be skipped to avoid false positives and for sake of performances. Now the scanner identifies which call frames are part of Red's code segment and skips the rest. However, one last hurdle remains, the dreadful compilation option in C compilers where the frame pointer is omitted in call frames (e.g. -fomit-frame-pointer in gcc). In such cases, walking the stack by dereferencing frame pointers is not an option anymore. The workaround is to save an extra "last known Red frame" pointer before calling any external code, which is then used by the scanner to jump over external code directly into the parent Red frame.


Node frames compaction

The GC is now capable of reclaiming node frames where the number of used slots is very low. It was, until now, a cause of memory leaking for long-running apps with bursts of high number of series allocations, as new node frames were allocated, but unused (non-empty) ones were never released.

This is now taken care of through a special GC pass that runs when specific conditions are fulfilled, moving live nodes from emptier frames to fuller frames, then freeing the entirely empty frames. The GC is then updating all references to relocated nodes during its marking and stack scanning stages.

In addition to that, the internal structure of node frames was improved. The free slots tracking method was changed from a stack-oriented model, to free slots linked-lists, resulting in doubling the node frames capacity, while keeping constant-time allocation/freeing performance.


External Red values reference management

Red values can sometimes be referenced by external non-Red code. The View engine relies on that and was storing copies of face object values inside external OS structures in order to be able to retrieve them on OS-generated events that would trigger Red callbacks. Such practice is not reliable and not compatible with the new node GC, as some node pointers could be stored away from the GC reach. So a new external values management system was introduced to only export a reference (an array index used as ID) to external code and keep all values inside Red series. The View backends were modified accordingly to rely on those references instead of copying the face object values.

That sub-system could in the future also be used for libRed external values management, to replace the ring buffer used there, which is functionally almost identical but now redundant.


Low-level allocations tracking

The Red runtime code sometimes has a need for allocating memory regions which last until the end of the Red process or need to be kept away for the GC. For that purpose, Red relies on malloc for such use-cases, just importing it from C library. Instead of a direct mapping, it now uses a thin layer on top of it in order to track all allocations providing extra features:

➤ Freeing of all system allocated memory regions on Red exit. This is not strickly needed for Red runtime, but allows to track eventual leaks (rare case as most of such allocations are permanent).

➤ Ability to gather stats about such allocations (reported in show/info in "allocated on heap" part).

➤ Buffer overflow detection in debug mode using guard barriers at the tail of allocated buffers and checked on freeing for eventual overflows.

➤ This layer is part of the R/S runtime, so available to R/S code too.


Other Changes

➤ stats native improvements: /info has been extended to contain also total allocated from OS and allocated memory from heap (see above). /show refinement has been implemented to pretty-print all those infos.

➤ Lowered memory allocations in Red runtime at start (about 1MB gained in total).

➤ Memory frames integrity auto-testing in debug mode (only node frames for now).

➤ Handle! values now hold a sub-type, revealed by mold/all (for debugging purposes):

    view [b: button "Hi!" [print mold/all b/state/1]]

    #[handle! 030A063Eh window] 

➤ Now the final buffer is preallocated internally for insert and append calls with /dup refinement, resulting in much lower memory usage.

➤ Using zero?, with a point3D value was always returning false due to an incomplete copy/paste change. Fixed now.

➤ Out-of-range integer math operations now promote results to float values.

➤ Updated GPIO definitions for RaspberryPi devices. Pi 4 should be supported, but untested yet. Pi 5 not yet supported (should be updated soon).

➤ Camera widget: now viewport aspect ratio is honored regardless of the container face size. Viewport is now centered and black bars are added if needed to fill the container face. A %camera-resize.red script is provided for testing.

➤ Toolchain: --view <engine> compilation option added to force a different View engine than the default one for the target.

➤ 47 tickets closed with a fix since 0.6.5.

➤ Stable releases are back!


Red/System changes

➤ Added system/lib-image to support libRedRT image properties.

➤ Improved native stack trace reports (frame address, stack records chaining support).

➤ On IA-32 backend, the passive casting mode (no conversion, all bits preserved) between integer! and float32! (as [integer!|float32!]keep)now returns the correct results.


What's next?


Next release (should be 0.7.0) will feature the full async IO support we are all waiting for! There are also other major features we're working on which probably will be released after 0.7.0.

In the meantime, enjoy this release!


June 11, 2024

Text-UI View backend

Last year, qtxie worked on a toy text backend project and submitted a PR for that. After some extra additions and testing recently, it has now been merged even if it is still incomplete, it is usable enough. So, in addition to the shiny GUI backends in Red/View, now we have an old-school text-based user interfaces (TUI) backend for the View engine!

The new TUI backend has currently a subset of the GUI backends features. Here is an overview:

  • View styles: base, panel, button, check, radiofield, text, progress, rich-text, image and text-list.
  • Draw commands: textlinebox, triangle, circle, ellipse (block-based for now).
  • Rich-text supported in Draw.
  • Keyboard handling: key-down and key events (which are the same event).
  • Mouse handling: disabled by default. Use system/view/platform/mouse-event?: yes to enable it.
  • Images support Truecolor (24-bit RGB) for image rendering if the terminal supports it, otherwise it falls back to 256 colors.
  • Timers supported through /rate facet.
  • Facets supported: /offset, /size, /text, /image, /color, /data, /enabled?, /visible?, /selected, /flags, /options, /pane, /rate, /para and /draw.
  • Flags supported: password and all-over.
  • Frames drawing using squared or rounded corners (
  • Limited ANSI escape codes support in /text facet, only Colors / Graphics Mode codes.
  • Uses 256 colors for text. It should works fine on most of the terminals.
  • Works on the big-3 platforms (Linux, macOS and Windows10/11).

The pre-built CLI console binaries on our Download page now have View/VID included by default along with the TUI backend. You can use them to test and play with the TUI code examples here and in the TUI folder.

To use the TUI backend in your own compiled code, you need to add the two following options in the Red header block:

    Needs:  'View
    Config: [GUI-engine: 'terminal]

Here are a few examples, starting with a HelloWorld!:

    view [text "Hello TUI World!"]

    Hello TUI World!

When view is invoked, an event loop is launched. In order to return back to the console prompt, press the Escape key.

Here is an animated example using a progress bar:

    view [
        bar: progress 30% rate 10 on-time [
            face/data: remainder (face/data + 10%) 100%
            info/text: form face/data
            info/font/color: random white
        ]
        info: text 4 font-color white "30%"
    ]

Spinners are also fun to watch:

Here is a rich-text example. Press TAB key to switch focus between the buttons. Press Enter key to push the button.

    btn-quit: rtd-layout [i/red ["Q"] "uit"]

    view compose/deep [
        rich-text 40x3 transparent data [
            yellow "Hello"  white red " Red "  green "World!^/"
            u "Underline" /u " " s "Strike" /s i " Italic" /i
        ] return
        button "button 1"
        button 4x2 draw [text 0x0 (btn-quit)] [unview]
    ]

An example of mouse support (not all the terminals have mouse support):

    system/view/platform/mouse-event?: yes

    view [panel 80x20 [base 11x1 center "drag me😀" loose]]

Simulating old-style text interfaces:

    view [
        panel navy 40x15 draw [
            pen off fill-pen black box 5x4 36x10
            fill-pen pewter pen black box 4x3 35x9
            pen red text 15x3 "Hello Red"
        ][
            origin 5x5
            rich-text 30x1 pewter data [
                green "Welcome" yellow " to" red " Red "
                u "TUI" /u blue " World!"
            ]
            return
            pad 12x1 button 4 "<OK>" [print "Hi!"]
        ]
    ]

File and folder requesters are also available in TUI, navigation is done using TAB and arrow keys, selection using Enter key:

Images support:

    url: https://upload.wikimedia.org/wikipedia/en/e/e9/Red_Language_Tower_Logo.png
    view [image url 32x15]

Here is the same image displayed in TUI next to the GUI version:


Some examples of 2D vector graphics using the Draw dialect (currently using only block graphics, braille-based graphics in the future):

    view [
        base 80x40 transparent draw [
            pen orange
            triangle 3x2 18x5 5x15
            fill-pen blue
            circle 30x8 5
            pen off
            fill-pen green
            ellipse 50x2 15x15
            pen brick fill-pen brick
            box 3x20 15x30
            pen gold
            line 20x30 28x20 40x28 44x24
        ]
    ]

A special mention to group-box widget. It has a couple of new options for the frame style:

    border-corners: round | #(none)
    border-color: #(tuple!)
Here is a usage example:
    view [
        group-box font-color green " Folders " 26x8 options [
            border-corners: round
            border-color: 255.0.0
        ]
        group-box " Files " 26x8
    ]


This TUI implementation is still not on par with our GUI backends. If some of you are motivated to extend and improve it, contributions are welcome! For example, we did not yet implement menus support. If someone is up to the task, please follow the GUI View menu dialect.

What's next?

We are finishing the work on some significant improvements to the Red and R/S memory management sub-systems and garbage collector that will bring them to the level required for a Red v1.0. Those changes will be released in a bumped 0.6.6 version. Those memory improvements are also needed for completing the work on the async IO branch.

Another version bump will follow with the deprecation of the high-level Red compiler and the addition of a new powerful layer to our Red tower of languages. All those changes are pre-requirements to start our work on 64-bit support.

In the meantime, enjoy this new toy!



May 27, 2024

Red in the real world

We're often asked what Red can be used for, or what apps have been written in Red. Red can be used to write almost anything, but the sparse ecosystem and some missing pieces limit certain use cases. It's used a lot for in-house data processing, custom DSLs, simple GUI apps, and more. We also used it to build Redlake's DiaGrammar product.

When we heard that someone had written a commercial app in Red, we thought that was great news, and we're here to tell you a little about it. Your first question is likely "What is it?" and the second "Where can I get it?". It's an XML processor, and you can find it here. The video on their site goes into detail about use cases and features, so we won't cover that here.


We asked the author to talk about why they wrote SmartXML why those chose Red for the implementation. Here's what they had to say:


Once I encountered the need to parse multiple XML files. I always thought that parsing tasks were very simple and that I wouldn't encounter any difficulties because there are things like XPath and XSD that, as I was told, solve all possible problems. However, I quickly realized that this was not the case, and some tools/standards only complicate life and are of little use for real-world usage. Thus, my XML parser project was born, which would allow working with real data rather than synthetic examples where XPath and XSD are truly effective.

I chose Red because I was tired of the complexity of 90% of modern languages and the constant breaking changes in many of them. If you were to ask me what language I would choose to start a project with, looking back, I would still choose Red or perhaps try to use Hare (even considering that it's not yet completed) simply because I want to be sure that my solution will work in 10 or even 20 years. Initially, I thought I could finish within half a year, but the project took me many years. Nevertheless, I brought the project to completion.

The main idea behind SmartXML was:

1. To make the parsing process as visual and simple as possible.
2. To abandon the use of XSD schemas, which create more problems than they solve.
3. To rethink XPath by creating a replacement that could work with complex structures.
4. To implement the ability to generate SQL from XML.
5. To implement batch processing of files.


And their advice on development in general:


During the writing process, I had a lot of time to think about what constitutes good code and a good product. Here are some of my thoughts that I realized while working on SmartXML:

1. Standard tools have standard problems. And people very often become hostages of such solutions. Most people prefer the shortest, not the most correct path.

2. The time spent on design is directly proportional to the lifetime of the software product. If you spend 10 years thinking about a problem from different angles, you are more likely to come up with an architecturally beautiful and cohesive solution, but maybe not on the first try. The easiest way to make your application 100-200 times heavier than necessary is not to try to think ahead, but to solve problems as they come.

3. You need to have the strength to admit mistakes. Even if they are design mistakes that require rewriting everything from scratch. Sometimes it is wiser to throw everything away and start over than to continue day by day moving into a dead end.

4. Sometimes it's better to do reengineering of the old instead of inventing something new.

5. A language that allows you to write code quickly solves tactical, not strategic tasks. A huge amount of code written in C 30 years ago will still be relevant in another 30 years. Rebol was designed for 20 years, so most of the code on it will still work with minimal modifications in 30 years.

6. Fighting complexity should take as much time as optimizing algorithms. Simple things are always obvious in hindsight. Writing complex code is always easier than writing simple code. Simple code will always be easier to extend and maintain, and it will always have fewer errors.

7. If you can sacrifice 10% of functionality at the cost of removing 90% of code, you should do it.


This application is a great fit for Red, whose `parse` function makes processing data easy (as much as XML processing can be) and clearly defined. The latter aspect is important, and often ignored. Can you write code to get that job done, maybe with regexes in this case? Yes. But can you maintain and extend that code? This is where dialects add enormous value. In a use case like this, being able to represent the data in Red format internally, for processing, also makes your life easier.

We thank the author of SmartXML for taking the time to talk about SmartXML with us, and we're excited to see what others do with it. Tell us about your project!

Happy Reducing!

February 19, 2024

0.6.5: Changelog

Bumping up the version number was motivated by the breaking syntax change done recently. We do not offer specific builds for a given version number anymore since we provide automatic builds (with builds history) on each new master commit. Though, the changelog for new version number changes will still be provided...and this one is pretty big as it covers about 5000 commits! Hope this will help users who might have missed some changes to catch up.

613 PRs were merged, 2415 fix commits were pushed, among which 902 are closing issues tracked on Github. The most notable new features and changes are listed below with eventual links to docs or previous blog posts describing or mentioning them:

Main new features

  • New datatypes: money!, ref!, point2D!, point3D!.
  • New codecs: RedbinJSONCSV
  • New high-performance run-time lexer with instrumentation support.(blog)(doc)
  • Interpreter instrumentation support (debugger, tracer, profiler).(doc)
  • New powerful APPLY native, with deep interpreter support.(blog)
  • Dynamic refinements support.(blog)
  • Adds compress and uncompress natives with gzib, zlib and deflate algorithms support.
  • Adds gpio:// port with GPIO dialect for RaspberryPi.(blog)
  • Adds TAB navigation support to View. (blog)
  • Adds raw strings syntax support.(doc)
  • Swaps map! and construction syntax. (blog)
  • Hashtables are now used for fast lookups in contexts.
  • Custom dtoa library implementation to load and form float values.
  • Standard library and garbage collector stability vastly improved.

Finished or almost finished features in branches::
  • Full IO ports with async support, including new IPv6! datatype.(branch)
  • TextUI backend to View.(PR)
  • XML codec.(PR)

Other general new features or changes

  • New natives: TRANSCODE, SCAN, AS-MONEY, ENHEX.
  • New functions: SINGLE?, LAST?, DT, TRANSCODE-TRACE, TRACE, CLOCK, NO-REACT, DO-NO-SYNC
  • New routines: SET-TRACE, TRACING?
  • Extends EMPTY? to support map! values.
  • Allows NONE as value in map!.
  • Adds REMOVE/KEY support for removing map! entries.
  • Adds FOREACH support for map!.
  • Add new-lines automatically when converting maps to blocks.
  • Supports issue!, money! and refinement! as key in map
  • Allows any-string! series to be matched against binary input in PARSE.
  • Adds Accept-Charset and User-agent to HTTP header.
  • Update libcrypto version requirement on Linux platforms.
  • Adds support for native port actors.
  • Extends ROUND/TO to support time!.
  • Adds a cross-platform threading library to Red runtime library.
  • Adds a FIFO MPMC queue to Red runtime library.
  • Adds history support to Red console.
  • Extends GET native to accept any-word! as argument.
  • FIND is now returning FALSE instead of NONE when used on bitsets.
  • Deprecates FIND on objects (redundant with IN).
  • Adds ABOUT function to console.
  • Preprocessor: fetch-next now supports set-words/paths, get/lit-args, object/series paths.
  • COMPLEMENT is now allowed on tuples.
  • Adds RANDOM/SECURE.
  • SORT support of /skip & /all & /compare integer extended to strings, vectors and binaries.
  • Extends FIND/LAST to support hash!.
  • Allows FINDing by datatype and typeset on hash! series.
  • Removes percent-encoding from files, use double-quotes when needed instead.
  • Adds SYSTEM/CATALOG/ACCESSORS.
  • Allows pair! for COPY/PART argument on series.
  • Allows LOOP and REPEAT to take a float argument as counter.
  • Extends REDUCE/INTO to support hash! destination.
  • Extends BODY-OF to support action! and native! values.
  • REPLACE reimplemented.
  • Adds support for REVERSE/SKIP.
  • Extends VALUE? native supports any-word! argument.
  • Enable image! in red/core on Linux.
  • Adds GC support to libRed.
  • Extends DISTANCE? to support pair! arguments.
  • Adds /KEEP refinement to TRY.
  • Implements RENAME action for FILE! datatype.
  • Adds routine arguments type-checking support to compiler.
  • Accelerates the output speed of LIST-DIR.
  • Allows error! values to be modified by users.
  • Adds [trace] and [no-trace] function attributs.
  • Faster/simpler EMPTY? function implementation.
  • Forces the inclusion of Git information in the runtime.
  • Optimization for appending values into hashs.
  • Extends CHANGE to support image! argument.
  • PICK on pair!, date! and time! values now support named accessors as index argument.
  • Adds PICK action to event! datatype.
  • Makes RECYCLE returns the allocated memory total by default.
  • Cleaner implementation of deep reactive paths support.
  • Internalizes SYSTEM/STATE/NEAR value.
  • Compound scalar datatypes (pair!date!time! and tuple!) will now emit ON-CHANGE events when one of their component is changed using an access path (both in compiled and interpreted code).
  • Adds reactivity support to bitset! values.
  • Adds support for REFLECT action on bitset! values.
  • Reports a proper path in compiled path errors.
  • Adds memory usage before/after a GC cycle in debug output.
  • Measures GC time in debug mode.
  • [ARM] Adds "division by zero" and "division overflow" checks in debug mode.

Parse

  • KEEP PICK on paren expressions now merges list of values to collected block.
  • Optimizes Parse's KEEP memory usage on strings/binaries.
  • Removes the end checking in iteration rules.
  • Speed optimizations for `TO <token>` rules.
  • Adds a fast path for `TO/THRU end` rules.
  • New set of optimizations for looping on a char! value in PARSE.

VID

  • Smarter merging of actors in a style with custom actors in the instance.
  • Added password flag for hidden input
  • Adds scrollable flag.
  • defines VID origin and spacing values per backend.
  • adds next and prev as default options to VID-made faces.

View

  • Adds a calendar widget.
  • Adds support for tri-state checkboxes.
  • Significant GTK backend improvements and upgrades to match other backends.
  • Better handling of DPI changes.
  • Handles pause and scroll-lock keys.
  • Now EVENT/PICKED is a float wheel delta-value in on-wheel event.
  • Improves user experience when closing window that contains a large number of faces.
  • Scale the font size with Ctrl + mouse wheel in GUI console.
  • Adds stop-events function to easily exit a View events loop.
  • Adds resize-ns and resize-ew mouse cursors.
  • Adds bounds option to /options facet for restricted dragging area.
  • Adds new /sync refinement to VIEW function. 
  • Adds /color facet info to DUMP-FACE output.
  • Improves memory usage when changing /draw facet content.
  • Adds support for semi-transparent no-border top-level windows (Windows).
  • Minimal dark mode support on GUI console.

Draw

  • Switch command parameters to point2D! and float! for subpixel precision.(blog)
  • New Direct2D backend for Windows.
  • Add line-pattern command for drawing dashed lines.
  • Extends scale command to support percent! values.
  • Supports image mapping to arbitrary quadrilateral.
  • Removes matrix-order command in DRAW.

Red/System

  • Adds subroutines to R/S functions.(blog)
  • Implements system/io/* instrinsics for CPU I/O read/write instructions.
  • Adds system/stack/push-all and system/stack/pop-all intrinsics.(blog)
  • Support for atomic operations using system/atomic/* intrinsics.(blog)
  • Adds #inline directive to R/S for including assembled binary code.
  • Implements support for integer hardware division (ARMv7+).
  • Generates optimized code for divisions by a power of 2 literal (ARM).
  • Vastly improved loop counter handling robustness.
  • Full support for special float values (-0.0, 1.#NaN, 1.#INF, -1.#INF).
  • Drops support for % and // operators on float types.
  • Adds support for function pointers in literal arrays.
  • Switches to 16-bytes stack alignment on Linux.
  • Adds log-2 function to standard library.
  • Allows cross-referenced aliased fields in structs defined in same context.
  • Support multiple variable assignments.
  • Allows grouping arguments and local variables type specification.
  • Relax lexical format of hexadecimal literals.
  • Allows get-paths pointers on function nested in contexts.
  • Adds support for simple pointer to pointer type.
  • Allows function! type to be specified for local variables.

Toolchain

  • Optimizes critical section in linker, twice faster linking time now on average.
  • Adds --show-func-map compilation option.
  • Various minor improvements to PE format support.
  • Adds working Linux-musl target.
  • Expands libRedRT to support View backend.
  • Adds --no-view option for Red binaries.
  • Shows the global words used by the toolchain in the compilation report.
  • Adds new Pico compilation target.

February 11, 2024

Important Change! Switching map and construction syntax.

Sometimes deep changes take a huge amount of code. Sometimes they take a lot of detailed explanation and consideration, leading to long discussions and people taking sides. Rarely does an important syntactic change to a language happen quickly, with universal agreement, simple implementation, and tools to help update scripts in the wild. Today is one of those rare days.

Admittedly, this idea has been discussed for a long time. It would surface, people nodded their virtual heads, and it would submerge again. Today it's ready to deploy. Not only that, but Rebol3 is making the same change, so the two languages will still be compatible in this regard.

What is the change?

It's easy to describe. Today, map! values use this syntax: #(...) and construction syntax (sometimes called serialized form or loadable form) looks like this: #[...]. Going forward, those syntactic forms will be swapped. Why? The answer is easy. In Redbol langs, blocks do not evaluate by default, you have to do or reduce them. Parens, on the other hand, do evaluate by default. Today, maps use paren-like syntax, but they do not evaluate, while construction syntax uses block-like syntax, but does evaluate. This is a carryover from Rebol, so the major concession here is that Red and Rebol3 will no longer be compatible with Rebol2's construction syntax.

If you've never heard of construction syntax, there's a nice explanation of it here. Red only supports a few values via construction syntax today, all datatype literals, true, false, none, and unset; but eventually it will support much more. If you look at the help for mold, you'll see that /all is TBD (very partially implemented for now), and that's how you create loadable, serialized, data that can safely and easily contain any value (like redbin but readable by humans). It helps avoid cases where none or true/false may load as words. This is also why construct evaluates those specific words (including also on/off/yes/no), but not others. When loading untrusted data, we have to strike a balance between ease of use and safety.

What do I have to do?

Not much. There are two tools available, which will convert your scripts automatically. The first is small and simple, showing just how powerful Red is, and leveraging its lexer instrumentation. You can find it here (once merged, that branch may go away and the tool will be in the main branch). The second is a more advanced and standalone tool written by @hiiamboris. You can find that here.

For the simple script it's necessary that you run it under a current version of Red's lexer. Once the change is in place, running it under the new lexer will make the exact opposite change. Of course, you can compile it into a standalone EXE, or use Boris' app, which is already available.

To run the simple script from a Red console, you can just:

  do https://raw.githubusercontent.com/red/red/master/utils/migration/map-conv.red

then you can use help map-conv to see all the available options. By default it runs in preview mode, making no changes, and showing you all the instances it found, which will be changed if you use the /save refinement. A copy of each changed file is created with a .saved extension in the same folder. If you don't want them, you can use the /no-copy refinement.

A word of warning, if you run the conversion tools a second time on the same files, they will convert the data back, because they can't know what you're thinking. On the bright side, this is an effective "undo" feature. Still, it's wise to back up your data before running any tools against them.

Thanks to the power of Red, and these tools, there are already PRs pending for updates to docs and community scripts. But the other thing you can do to help is to let us know when you find things that need to be updated for this change, and especially if you run into any issues when converting your own code.

Conclusion

We know changes like this can be hard, but better now than when there is even more code in the wild that would be affected. If we had been any other language, this long-view improvement might not have happened. Only because Red (and Redbol langs in general) can consume their own code as data and have powerful parse and lexing features, was this change so easy and safe. It's still a code-porting process, and if you run multiple versions of Red you may need to maintain separate versions for a while. The other hard part is retraining your hands and eyes to the new syntax.

Happy Reducing!

November 22, 2023

Tab Navigation

We finally got tab navigation implemented! You might think it should have been an easy feature to add, but achieving a consistent and controllable behavior across our different native GUI backends is not that straightforward. So we opted for a mixed implementation with a general high-level navigation layer in Red and left spatial navigation handling to each backend, in order to preserve the native behavior as much as possible.

Automatic navigation

By default, pressing TAB key will allow you to navigate to all the GUI widgets in a window, capable of acquiring the focus. Once the last widget is reached, the next TAB press will circle back to the first focusable widget. Conversely, back-navigation can be achieved using Shift-TAB key combination, circling from first face to last one. Here is a simple example:

    view [
        text  "Name"     field focus return
        text  "Surname"  field return
        below
        check "Single"
        check "Employed"
        button "Send"
    ]

Note: check-boxes selection/unselection is done using the Space key (default on Windows).


It is possible to make a face "TAB-transparent", so that TAB navigation will skip it in both directions. This is achieved by removing the focusable flag from a navigable face. For example, in the following code, clicking on the "Click me!" button will toggle the button's focusable flag on and off (using set-flag/toggle):

    view [
        text "Name"     field focus return
        text "Surname"  field return
        below 
        check "Single"     
        check "Employed"   
        button "Send"
        button "Click me!" 100 [
            face/text: pick ["TAB ignore" "TAB stop"] to-logic face/flags          
            set-flag/toggle face 'focusable
        ]
    ]


In case of area face, the default behavior for TAB navigation means that tab characters cannot be input in the area. In such cases, the alternative Ctrl-TAB key combination can be used to input tab characters. In case the focusable flag is removed from an area face, then TAB key will directly produce tab characters. Here is an example:

    view [
        text "Name"     field focus return
        text "Surname"  field return
        below 
       	text "Comments" com: area 
        button "Send"
        button "Toggle Area" [set-flag/toggle com 'focusable]
    ]

Note: when the focusable flag is on, Ctrl-TAB is used to input tab characters, when it's off, it's just using TAB key.


Manual override

In some cases, the user can decide to set a different path for keyboard navigation. For each navigable face (the ones with a focusable  flag), it is possible to manually define the next and/or previous one when tabbing forth and/or back. In order to do so, next and prev options can be set to define how tabbing will navigate to the next or previous face.

Here is a simple example where the default navigation is changed to jump into fields marked as invalid or empty (using pink background) after a typical form submission:

    view [
        group-box 2 [
            style error: field pink
            text "Name"     field "John"
            text "Surname"  field "Smith"
            text "Age"      error "abc" focus
            text "Address"  error "-"
            text "Zip code" field "12345"
            text "City"     error
            text "Country"  error
        ] return
        btn-send: button "Send"
        do [
            list: collect [foreach-face self [if face/color = pink [keep face]]]
            forall list [list/1/options/next: list/2]
            btn-send/options/next: list/1
        ]
    ]

Notes:

  • For the sake of simplicity in this example, only forward navigation is restricted, backward navigation will visit all focusable faces.
  • In the do block, self refers to the window face, as do denotes a global section, not widget-related.
  • When list/1 refers to the last element, list/2 returns none, so it does not point to any specific face. In such case, the default tab navigation will automatically (and conveniently) select the next face, which is the "Send" button.
  • The last line is there to connect that last face (the button) to the first face in our restricted list.


Other notable changes

An important change concerns the insert-event-func function specification, it now requires a name as argument:

    >> ? insert-event-func
    USAGE:
         INSERT-EVENT-FUNC name fun

    DESCRIPTION: 
         Adds a function to monitor global events. Returns the function. 
         INSERT-EVENT-FUNC is a function! value.

    ARGUMENTS:
         name         [word!] 
         fun          [block! function!] "A function or a function body block."


The name is an arbitrary word that only needs to be unique, so it becomes easier to check if a given global handler has been installed or not. It also makes it easier to remove it, as it can be referred by name in remove-event-func. Existing handler names can be checked using:

    >> extract system/view/handlers 2
    [tab field-sync reactors radio enter debug dragging]

Please update your code if you have been using those functions.

Enjoy!

August 9, 2023

Subpixel GUI

Maybe you didn't notice, but Red/View, our GUI engine, has subpixel precision from the beginning! Unfortunately, that level of precision was not directly accessible to end users, until now. 

Actually, it would be more accurate to say that we had subpixel resolution only so far. The guilty part is the pair! datatype being limited to integer components only, while subpixel precison requires decimal numbers. So we have recently introduced new datatypes to cope with that.

What urged us to make those changes now was a very peculiar visual glitch caused by that dissonance. That glitch happens during face dragging operations. Here is an example using our View test script:

As you can see, on some positions, the face starts shaking while the mouse cursor remains still. This affects any type of face. The shaking is about ±2 pixels. It is caused by the difference in precision between the /offset facet expressed in integer numbers and the backend API, which only deals with floating point numbers. The accumulated error when converting integer->float->integer gives a 2 pixels difference. Such error happens on displays where the scaling factor is different from 100%. With the rise of 2K, 3K and 4K displays, a scaling factor > 100% has become the norm, making this glitch more frequent. You might think that this is not a big issue until you start building custom scrollbars and see your entire scrolled content shaking massively...

New point datatypes

In order to provide decimal positions and sizes for View faces, extending the existing pair! datatype was considered, though, the pair syntax can hardly scale up for such needs:

    2343.122x54239.44
    2343.122x54239.44x6309.332
    2343.122x54239.44x6309.332x442.3321
    2.33487e9x54239.44
    2.33487e9x54239.44x9.83242e17
    2.33487e9x54239.44x9.83242e17x5223.112
    1.#infx1.#infx1.#inf

As you can notice there, it quickly becomes difficult to read and identify the individual components. So we opted for adding a new literal form (hence a new datatype) that matches how coordinates for two or more dimensions are commonly represented:

    (2343.122, 54239.44)
    (2343.122, 54239.44, 6309.332)
    (2343.122, 54239.44, 6309.332, 442.3321)
    (2.33487e9, 54239.44)
    (2.33487e9, 54239.44, 9.83242e17)
    (2.33487e9, 54239.44, 9.83242e17, 5223.112)
    (1.#inf, 1.#inf, 1.#inf)

Such literal forms requires the comma character to be a delimiter, so that it cannot be used anymore as a decimal separator. That was, unfortunately, a necessary decision in order to unlock such literal forms. The gains should be bigger than the loss.

So, two new datatypes have been added:

  • point2D!: a two-dimensional coordinate or size.
  • point3D!: a three-dimensional coordinate or size. 

Their canonical lexical forms are:

    (<x>, <y>)
    (<x>, <y>, <z>)

    where <x>, <y> and <z> are integer or float numbers.

Optional spaces are allowed anywhere inside the point literals on input, they will be removed on loading.

    >> (1,2)
    == (1, 2)
    >> (  1.35 ,  2.4  )
    == (1.35, 2.4)

Both for 2D and 3D points, their components are internally stored as 32-bit floating point numbers, so that their precision is limited to 7 digits. This should be far enough for their use-cases though.

When one of the components has a fractional part equal to zero, it is displayed without the .0 part for easier reading. Similarly, integers are accepted as input for any component and are internally converted to a 32-bit float.

    >> (0.3, 0.5) + (0.7, 0.5)
    == (1, 1)
    >> (2.0, 3.0)
    == (2, 3) 

Creation

Besides literal points, it is possible to create them dynamically, the same way as pairs, using make, to or one of the as-* native functions:

    >> make point2D! [2 4.5]
    == (2, 4.5)
    >> to-point2D 1x2
    == (1, 2)
    >> as-point3D 1 (3 / 2) 7 * 0.5
    == (1, 1.5, 3.5)

Accessors

Point components can be individually accessed using ordinal numbers or component names using action accessors or path syntax:

    >> pick (2, 4.5) 1
    == 2.0
    >> pick (2, 4.5) 'y
    == 4.5
    >> p: (2, 4.5)
    == (2, 4.5)
    >> p/x
    == 2.0
    >> p/y: 3.14159
    == 3.14159
    >> p
    == (2, 3.14159)

Math operations

Basic math operations are supported as well:

    >> (1, 1) + (2, 3.5)
    == (3, 4.5)
    >> (1, 1) - (2, 3.5)
    == (-1, -2.5)
    >> (2, 3) * (10, 3.5)
    == (20, 10.5)
    >> (20, 30) / (10, 3)
    == (2, 10)

Notice that mixing pairs with point2D in math expressions is allowed. The pair value will be promoted to a point2D in such case (as integers with floats):

    >> 1x1 + (2, 3.5)
    == (3, 4.5)

Other actions/natives

    >> round (2.78, 3.34)
    == (3, 3)
    >> round/down (2.78, 3.34)
    == (2, 3)
    >> random (100, 100)
    == (53, 81)
    >> zero? (0, 0)
    == true
    >> min (10, 100) (24, 35)
    == (10, 35)
    >> max 10x100 (24, 35)
    == (24, 100)    
Notice that pairs will be promoted to point2D in mixed use cases with min/max.

 

View and VID adjustments

The main changes are in face! object:

  • /offset: now only accepts point2D! values.
  • /size: accepts both pair! and point2D! values.

In VID, both pair and point2D values can be used to denote positions and sizes, so that VID is backward compatible. All previous VID code should work without any change. VID will convert all positions to point2D values. Sizes by default in VID, keep using pairs, unless a point2D is provided by the user.

All Draw commands that were accepting pairs now also accept point2D values for higher precision.

The related documentation will get updated soon to reflect those changes.

In order to illustrate the difference in using pairs and point2D positions, here is a (not so) simple animation comparison showing the subpixel positioning difference (correctness of animation in this case is privileged over simplicity of code):

    view/no-wait [
        size 800x200 space 0x0
        b1: box 2x40 red return
        b2: box 2x40 blue
    ]
    x: b1/offset/x
    until [
        do-events/no-wait                   ; processes GUI events in queue
        wait 0.1                            ; slows down the animation
        do-no-sync [                        ; switches to manual faces redrawing
            b1/offset/x: b1/offset/x + 0.1
            b2/offset/x: to-integer x: x + 0.1
            if all [b1/state b2/state][show [b1 b2]] ; redraws both faces
        ]
        any [b1/offset/x > 700 none? b1/state none? b2/state]
    ]

Here's the zoomed capture of the result (on a display with 200% scaling factor):


The red bar uses the newly enabled subpixel precision, while the blue bar simulates the old pair positioning precision (so only allowing integer positions). What you can see is that the red bar makes two smaller steps while the blue bar makes a single one, looking more "jumpy".

This means that now animations on displays with a scaling factor > 100% can be smoother as they benefit from more accurate positioning.

Note: the animation code above is far from being simple or elegant, we'll be working on improving that. The animation code could have been quite simpler by using a rate option in VID and putting the animation code in a on-time handler. Though, timer events firing (especially on Windows) are not very reliable, so unrolling a custom event loop lowers that risk when the timing is critical (like for fast game loops).


As a conclusion, here is an old-school style starfield code demo using 2D and 3D points (moving mouse left/right changes the stars speed):

Let us hear your feedback about those changes on our Gitter (now Matrix) channel.

Enjoy!

Fork me on GitHub