GUI Development, best practices/methods?

Hi,

I'm looking to make a GUI for a Rust plugin and wondering what the best way of doing it is. 

Is the only way to make UI's using this 'Cui'? Someone mentioned that there might be an alternative, but not they're not sure of its name etc. If so, which method is preferrable? 

From what I have seen of Cui so far, people seem to either use a massive Json string @"" version and replace things inside of it at runtime like what command to run or parameters to pass in like 'Hello $username!' etc. Or alternatively, most of the plugins that I've downloaded and had a look at with gui's seem to format their code like this 
bCh66TEiUpCrZF9.png

Is there a reason for not writing it like the following:

            CuiElementContainer container = new CuiElementContainer();

            CuiPanel panel = new CuiPanel();
            panel.Image.Color = "0 0 0 0";
            panel.RectTransform.AnchorMin = "0.25 0.25";
            panel.RectTransform.AnchorMax = "0.5 0.5";
            panel.CursorEnabled = true;
            container.Add(panel,"Overlay","basicPanel");​

Or is it just preference and more tutorials are available for the former method?

Cheers & any help is much appreciated!! 

There is only one way to show a UI on a client, which is for the server to send the client some JSON describing the elements to create/destroy/update. However, there are multiple ways to create and send such JSON.

The simplest and most common way to create and send UI JSON is by modeling the elements and components in plain C# classes, creating a collection of temporary objects of those classes to describe the desired UI, then using the Newtonsoft JSON library to serialize that collection into a JSON string, and finally sending that string to one or more clients using a specific RPC call to the client. This approach is most commonly implemented using the Cui* classes provided by the Oxide framework. However, this approach leaves much room for optimization, which I will outline below.

  • Creating short lived objects generates a significant amount of garbage (i.e., objects that are no longer referenced by other objects), which increases work for the garbage collector and increases the frequency of garbage collection lag spikes. This can be avoided by either pooling objects or by carefully using structs instead of objects.
  • Creating JSON strings generates a significant amount of garbage. As strings are immutable in .NET, it's not possible to modify a string for reuse, meaning any time you need to send a different variation of the UI, you need to create an entirely new string. Since the RPC call to the client will eventually convert the string to bytes before sending, the string is not strictly necessary. You can cut out the intermediate string by serializing directly to bytes.
  • Newtonsoft JSON serialization is relatively slow. Alternative libraries exist such as System.Text.Json, but that one is not available on Rust servers by default. You can also implement a custom JSON serializer fairly easily, on the basis that it only needs to support serialization (no deserialization), and only needs to support a few levels of object nesting (due to the limited CUI specification).
  • Oxide's CuiHelper only supports sending a UI to one player at a time, resulting in duplicate string->bytes conversions if sending to multiple players. The duplicate string->bytes conversions can be avoided by making the RPC call directly, and passing it a list of player connections to send the data to.

To learn more about the CUI specification (i.e., what the client accepts), see the following documentation written by a community member (Kulltero).
https://github.com/Kulltero/Rust.Community/tree/Docs

Besides Oxide's CUI, various community members have created alternative server-side CUI implementations. Some private, some public. I'll list a few here for reference.

  • https://github.com/dassjosh/Rust.UIFramework -- UI Framework created by a community member (MJSU).
    • Custom serializer, 10x faster serialization than Newtonsoft
    • Supports sending UI to multiple clients at once without duplicate string->bytes conversions
    • Provides object pools for modeling UIs as objects without generating garbage
    • Provides a vector cache to avoid generating garbage for repeat Vector3->string conversions
    • Provides some higher level elements that abstract away the smaller CUI building blocks
  • https://github.com/WheteThunger/Backpacks/blob/34f1be40daf295de79dc8f6a57798edd4fae5bbf/Backpacks.cs#L2248 -- Custom UI builder in the Backpacks plugin.
    • Same improvements as the above framework, but does not provide higher level of abstractions
    • Uses structs instead of pooled objects as the means to avoid garbage allocation, but this comes with some minor limitations
    • Provides much closer syntax to the Oxide CUI library, making it relatively easy to transition older code
  • k1lly0u has some sort of framework or library in his Chaos Extension used by many of his plugins, but I haven't looked at it, so unsure what it can do or if it's available for general use (it might be obfuscated and/or not licensed for 3rd party use).

Thanks for a quality write up! Super interesting read and should probably be stickied somewhere into a GUI FAQ haha! 

Taking a look at the Rust UI Framework by MJSU is interesting, the speed comparison seems to mainly be in the serialization of data, would we still see a benefit if I was to generate the UI at script startup, store the UI as serialized JSON in the data folder and then when requested by the users chat command, just send that pre-serialized json from data file? 

What about even generating that UI, serializing and then pre-storing as bytes? Would we still see a speed benefit from using something like UI Framework? 

Also, does directly sending the data via RPC have any benefit over the CuiHelper for simple one page static guis? Other than obviously being able to send to many at once, for something like a simple /help gui that a user might want to view and not something like a HUD overlay to send to many players at once etc.

Yes, the speed comparison is for serialization.

It's worth clarifying that the advantage to the above frameworks/libraries is that you get to continue using the same building blocks to build high performance UIs without having to concern yourself with use case specific optimizations.

You can, for some use cases, store the strings or preferably bytes in memory, and even modify the bytes in place or splice together separate byte chunks to see even greater performance gains, but such optimizations are difficult to use for UIs of high complexity.

You can also combine those ideas by using a high performance library/framework to build the UI, then cache it as bytes, but you can avoid introducing hundreds of lines of code into your plugin by just using the Oxide CuiHelper for building the initial UI, then apply your use case specific optimizations.

Yeah I think building the UI and caching it is definitely the way to go for me! For me it's not something that needs to be created at runtime as there shouldn't be anything really changing on my UI. Just a help screen with some buttons to run chat commands. 

On a bit of a sidenote, I was looking through your gui section with the Backpacks plugin and noticed you were using an unlisted hook 'OnNetworkSubscriptionsUpdate' Is there a list of unlisted hooks like this? Or is this just a deprecated hook but still available for compatability? And OnNetworkGroupEntered/OnNetworkGroupLeft have superseeded them? I have a constant fear of 'not knowing' whats out there and feel like i'm just struggling with everything i try make haha! 

OnNetworkSubscriptionsUpdate is a new one. I just haven't really documented new hooks for the past year. Typically I don't advise people to start with hook documentation, but rather to start by looking at the assemblies to understand the code you are interacting with and what you want to change, and while doing so, you will see which hooks are available in the assembly.

Hi There,

I was planning to make a small plugin using a GUI to show what events will be happening on the server for the wipe. Literally just Event name, Event date, Event description and a picture. The plan was to have a config file with this info and update it when needed. I have been struggling with this for weeks now as I am an old VB6 programmer but never got into .NET or C#. After reading the above posts, I am better off posting it in my discord rather than in-game lol. Did not realize that just displaying a GIU on Rust is so complicated. Thank you for the detailed explanation, even if it was mostly over my head.

PS.
I have a new-found respect for plugin developers.