Languages

Unity Runtime UI – Crib Notes

Initial Setup

Every UI Document component references a UI Document asset (.uxml file) that defines the UI and a Panel Settings asset that renders it. You can connect more than one UI Document asset to a single Panel Settings asset.

Right-click Project window, and then select Create > UI Toolkit > UI Document

  1. In the Hierarchy right click the Scene, select GameObject > UI Toolkit > UI Document. This creates the following:
    • A UI Toolkit folder with a Panel Settings asset and a default runtime theme.
    • A GameObject with a UI Document component which in turn the is connected to the Panel Settings asset.
  2. In the Hierarchy select the UIDocument  then drag SimpleRuntimeUI.uxml from your Project window to the inspector (Source Asset)
  3. Create a companion script that derives from MonoBehaviour.
  4. Add SimpleRuntimeUI.cs as a component of the UIDocument GameObject.
  5. Click on the PanelSettings object Project > Assets > UI Toolkit then drag the theme style sheet (.tss) to the Panel.
  1. Use the Package Manager to install the Input System package.
  2. Select Edit > Project Settings > Player.
  3. Set Active Input Handling to Input System Package (New).
  4. Select GameObject > UI > Event System. This adds an Event System GameObjectThe fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
    See in Glossary that includes a Standalone Input Module in the Scene. The module shows an error message along with a button in the Inspector window.

Custom USS

  • Right click Project Create UI ToolkitStyle Sheet
  • Add style sheet to umxl view
    • Add a Style tag with src attribute pointing to the style sheet
    • In the UI Builder upper left corner (+▼) → Add Existing USS

Using UXML instances as templates

Any UXML file in the project is available as a library object in the UI Builder.

Editing Templates

Open in UI Builder

Library→ProjectComponent (RMB)→Open UI Builder

Close the current context and open the component in the UI Builder.

Open as Sub-Document

Hierarchy→Component (RMB)→Open Instance in Isolation

Edit the component in the UI Builder, maintaining a breadcrumb to the previous context.

Open Sub-Document in Place

Hierarchy→Component (RMB)→Open Instance in Context

Edit the component while keeping the view on the current component. Maintains a breadcrumb to the previous context.

Common Attributes

  • name
  • tooltip
  • tabindex

Accessing UXML Elements in C#

Typically a controller for a UI Element will is added to the UI Document as a component. Alternately the the UI Document can be set as a field on a MonoBehaviour instance.

Extending the MonoBehaviour class is most expedient way to control a UI component when it has only one instance. Public accessors permit access from other controllers, and should be used sparingly.

Select UI Document, in the Inspector→Add Component→Scripts.

using UnityEngine;
using UnityEngine.UIElements;

public class GameListController : MonoBehaviour {
    public string GameListID = "GameList";

    private VisualElement Root {
        get { return GetComponent<UIDocument>().rootVisualElement; }
    }

    private ListView GameList{
        get { return Root.Q<ListView>(GameListID); }
    }
}

Add event handlers to the MonoBehaviour Start method.

public void Start() {
   Root.Q<Button>(JoinButtonID).RegisterCallback<ClickEvent>(ClickJoinButton);
}
public UIDocument uiDocument1;
[SerializeReference] private UIDocument uiDocument2;
var uiDocument = GetComponent<UIDocument>();
public VisualElement Root {
    get { return GetComponent<UIDocument>().rootVisualElement; }
}

Custom Control Class for UXML Elements [source]

Create a C# class derived from the VisualElement class or a subclass.

You can initialize the control in the constructor, or when it’s added to the UI.

Event Handling

https://docs.unity3d.com/Manual/UIE-Events.html

You can register an event handler on an existing class, to handle events such as a mouse click.

Dynamically Adding Elements

ScrollView scrollView = Root.Q<ScrollView>(GameListID);
scrollView.contentContainer.Add(new Label("I done got clicked"));

External Links

UXML Elements Reference

Override VisualElement.ContainsPoint() to assign custom intersection logic. [source]

You can also create USS custom properties to style a custom control. [source]

https://docs.unity3d.com/2022.2/Documentation/Manual/UIToolkits.html

https://docs.unity3d.com/2022.2/Documentation/Manual/UIE-get-started-with-runtime-ui.html

https://docs.unity3d.com/ScriptReference/UIElements.VisualElement.html

https://docs.unity3d.com/Manual/UIE-USS.html

https://docs.unity3d.com/Manual/UIE-create-tabbed-menu-for-runtime.html

MySQL in C#

Package Introduction

Database access is a core aspect of many applications. C# can interface with many different databases such as Oracle, Microsoft SQL, and MongoDB. In this case, we will be working with MySQL. The first step is to add the MySQL package to your project.

dotnet add package MySql.Data

Saving Your Credentials

When you run a client program to connect to the MySQL server, it is inadvisable to specify your password in a way that exposes it to discovery by other users. We will store our credentials in a .env file and load them into the environment variables. The code to load .env files is trivial, so we will just add it directly to the project. Add the .env file to your .gitignore so that you don’t upload your credentials to your code repository.

class DotEnv {
    public static void Load() {
        string filepath = Path.Combine(
            Directory.GetCurrentDirectory(),
            ".env"
        );

        DotEnv.Load(filepath);
    }

    public static void Load(string filePath) {
        if (!File.Exists(filePath)) return;

        foreach (var line in File.ReadAllLines(filePath)) {
            var parts = line.Split('=',StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length != 2) continue;
            if (Environment.GetEnvironmentVariable(parts[0]) != null) continue;
            Environment.SetEnvironmentVariable(parts[0], parts[1]);
        }
    }
}

Example .env File

SQL_USER=username
SQL_PW=supersecret
SQL_SERVER=localhost

Creating a Connection

Before you can perform database operations, you need to establish a connection. This is achieved with a MySqlConnection object. We will add the variables from the .env file with string interpolation.

using MySql.Data.MySqlClient;

DotEnv.Load();
var env = Environment.GetEnvironmentVariables();

string cs = @$"
               server={env["SQL_SERVER"]};
               userid={env["SQL_USER"]};
               password={env["SQL_PW"]};
               database={env["SQL_DB"]}
            ";

var con = new MySqlConnection(cs);
con.Open();
Console.WriteLine($"MySQL version : {con.ServerVersion}");
con.Close();

Prepared Statements

After the connection has been established you will typically perform one or more operations, this is enabled by the MySqlCommand object. There are three primary methods that you will usually use:

  • ExecuteReader: Query the database.
  • ExecuteNonQuery: Insert, Update, Delete data.
  • ExecuteScalar: Retrieve a single value.

Although possible to build SQL queries using string interpolation, it is not advisable to put query values directly in the query string. Doing so leaves your database open to injection attacks. Instead of placing the value into the query directly, we will use placeholders, prefixed with ‘@’.

Unsafe

string sql = $"SELECT Name FROM Country WHERE Continent = {continent};

Safe

string sql = "SELECT Name FROM Country WHERE Continent = @continent;

When using parameterized strings the values are added using the Parameters field of the MySqlCommand object.

Update the Database

To insert a new record into the database we use the ExecuteNonQuery method.


MySqlCommand.ExecuteNonQuery()

Executes a SQL statement returning the affected row count. [*]

        string sql = "INSERT INTO users(username, password, email) values (@username, @password, @email)";
        using var cmd = new MySqlCommand(sql, con);
        cmd.Parameters.AddWithValue("@username", username);
        cmd.Parameters.AddWithValue("@password", password);
        cmd.Parameters.AddWithValue("@email", email);
        cmd.ExecuteNonQuery();

Query the Database

Sends the CommandText value to MySqlConnection and builds a MySqlDataReader object.

The Reader object contains the rows that result from the SQL command execution. The Read method advances the reader to the next row, returning true if a row exists. The using keyword ensures that classes that implement the IDisposable interface call their dispose method.

using (MySqlDataReader reader = cmd.ExecuteReader()){
    while (reader.Read()) {
        ...
    }
}

You retrieve values from the current row with the class indexer which can retrieve values by column index or name. There are also several methods to retrieve a value by type, for example, GetInt32. The following statements perform the same action.

var name = reader[0];
var name = reader["name"];
var name = reader.GetString(0);
var name = reader.GetString("name");

External Reading

Official MySQL/Net Tutorial

https://dev.mysql.com/doc/connector-net/en/connector-net-tutorials-sql-command.html

Nuget MySQL Package

https://www.nuget.org/packages/MySql.Data/

MySQL/Net API

https://dev.mysql.com/doc/dev/connector-net/8.0/

MySQL Server on Ubuntu 22.04 (WSL)

Install and Setup

sudo apt update
sudo apt upgrade
sudo apt install mysql-server
sudo service mysql start
sudo service mysql status
sudo mysql
sql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by 'password';
sudo mysql_secure_installation

Add User

mysql -u root -p
sql> CREATE USER 'MyUsername'@'localhost' IDENTIFIED BY 'password';
sql> CREATE DATABASE datebase_name;
sql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON database_name.* TO 'MyUsername'@'localhost';
sql> quit

Access Database

mysql -u MyUsername -p
sql> show databases;
sql> use database_name;
GRANT ALL ON demo.* TO 'demo'@'localhost';
mysql --login-path=demo

https://www.prisma.io/dataguide/mysql/tools/mysql-config-editor#:~:text=information%20in%20the%20.-,mylogin.,client%20%2C%20if%20it%20is%20defined.

C# JSON Client-Server Implementation

Background

A Socket is one end of a two way communication link. The major protocols for socket communication are TCP and UDP. The primary difference is that TCP guarantees data delivery and the order of data packets. UDP does not make such guarantees, but as a consequence it is faster. Developers typically default to TCP.

The C# socket library is found inthe System.Net package.

using System.Net;
using System.Net.Sockets;

IP Endpoint

An IPEndpoint is the pairing of an IPAddress and a port number. You can use DNS lookup to obtain an IP address.

IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("google.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint ipEndPoint = new(ipAddress, 7000);

When creating the server endpoint, you can specify ‘any’ for the ip address.

IPEndPoint ipEndPoint = new(IPAddress.Any, 7000);

Terminate a Socket

Shutdown disables sends and receives on a Socket.

Close will terminate the Socket connection and releases all associated resources.

socket.Shutdown(SocketShutdown.Both);
socket.Close();

Server

A server must first listen for and accept connections. Then, when a connection is made, listen for data on a seperate Socket.

Creating a Socket

Bind associates a socket with an endpoint.

Listen causes a connection-oriented (server) Socket to listen for incoming connection attempts.

Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ipEndPoint);
socket.Listen(port);

Accepting Connections

Accept blocks until an incoming connection attempt is queued then it extracts the first pending request from a queue. It then creates and returns a new Socket. You can call the RemoteEndPoint method of the returned Socket to identify the remote host’s network address and port number.

See AcceptAsync for the asynchronous accept call.

Socket handler = this.listener.Accept();

Client

You create the client side socket in the same manner as the server. The difference being, instead of Bind and Listen, the client uses Connect.

IPAddress ipAdd = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPt = new IPEndPoint(ipAdd, port);
Socket socket = new Socket(ipAdd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ipEndPt);

Reading & Writing

Reading and writing date on a socket is the same for both the server and client side socket. Once the actual connection is made, the difference between the two is arbitrary. Both the read and operations require a byte array to act as a buffer. So your data will need to be converted to and from an array of bytes.

Write

The Send method is used to write to a socket. While there are a number of different method flavours, the most common is to send the entire contents of a byte array.

byte[] msg = Encoding.ASCII.GetBytes(aString);
this.socket.Send(msg);

If you are using a connection-oriented protocol, Send will block until the requested number of bytes are sent, unless a time-out was set by using Socket.SendTimeout. If the time-out value was exceeded, the Send call will throw a SocketException

If the receiving socket (server) has not been started a SocketException will be thrown. If the server has been started but Accept has not been called reading and writing will hang. This can be remedied by setting a timeout (in ms).

Set Read/Write Timeouts

socket.ReceiveTimeout = 1000;
scoket.SentTimeout = 1000;

Read

If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, the Receive method will complete immediately and return zero bytes. This allows you to detect a clean shutdown.

There are many flavours of the Receive method, but we will only concern ourselves with two of them. The first reads all bytes from a socket. It returns the number of bytes read. You will need to provide your own EOF indicator or wait until 0 bytes are read which means the socket has finished writing and closed. This is useful if you are only connecting the sockets for a single read-write operation.

byte[] bytes = new byte[BUFFER_SIZE];
socket.Receive(bytes);

The second, and the one we will be using, is to read a specific number of bytes from the socket.

int count = socket.Receive(bytes, nextReadSize, SocketFlags.None);

We will use this to first read the size of the data, then read the body of the data.

// read size
byte[] bytes = new byte[INT_BUFFER_SIZE];
socket.Receive(bytes, INT_BUFFER_SIZE, SocketFlags.None);
int size = BitConverter.ToInt32(bytes, 0);

// read message
byte[] bytes = new byte[size];
socket.Receive(bytes, size, SocketFlags.None)
string data = Encoding.ASCII.GetString(bytes, 0, count);

Implementation

JSON

The json encoding and decoding will be handled by the Newtonsoft Json.Net library.

dotnet add package Newtonsoft.Json --version 13.0.1

The include statements for this library are.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Layout

There are 3 source files in the project:

  • Server.cs: Manages new client connecting to the server and emits Connection objects.
  • Connection.cs: Wraps a socket providing the read-write capabilities.
  • Client.cs: A connection object that connects to its own socket.

Server

Most of the details for this class are provided in the above background section. The main detail here is the way new connections are managed. I have put the loop inside a IEnumerable method. This should be looped to hand off new connection objects. The following example does this in a new thread.

Thread thread = new Thread(new ThreadStart(()=>{    
    foreach(Connection connection in server.Connections()){
        connection.WriteString("ack");
        connection.Close();
    }
}));

Connection

The connection object reads and writes JSON objects or Strings in two parts. First it writes four bytes representing the integer size of the data. Next it writes the data it’s self.

Client client = new Client().Connect("127.0.0.1", 7000);
client.socket.ReceiveTimeout = 3000;
Console.WriteLine("> " + client.ReadString());
server.Stop();

C# Creating a Class Library

This instruction post is an abbreviation of Microsoft tutorial found HERE.

Contents

  • Create the Library
  • Using the Library
  • Unit Testing
  • Including the .dss Directly
  • Setup

    Create an SLN (project solution file)

    An SLN is a Visual Studio solution file that keeps information about the organization of projects in a file.

    dotnet new sln

    Create the class library project

    The -o (–output) flag specifies the directory to place the project. It will create the directory.

    dotnet new classlib -o StringLibrary

    Add the project to the solution

    dotnet sln add StringLibrary/StringLibrary.csproj

    Add code to the library

    Add the following code to the StringLibrary/Class1.cs file

    namespace UtilityLibraries;
    
    public static class StringLibrary
    {
        public static bool StartsWithUpper(this string? str)
        {
            if (string.IsNullOrWhiteSpace(str))
                return false;
    
            char ch = str[0];
            return char.IsUpper(ch);
        }
    }

    Build the solution

    dotnet build

    Using the Library

    Create a console app

    dotnet new console -o ShowCase

    Add the console app to the solution

    dotnet sln add ShowCase/ShowCase.csproj

    Modify the ShowCase/Program.cs file

    using UtilityLibraries;
    
    class Program {
        static void Main(string[] args) {
            foreach(string s in args){
                var uc = s.StartsWithUpper();
                Console.WriteLine($"String {s} starts with uppser case {uc}");
            }
        }
    }

    Add a ‘StringLibrary’ reference to ‘ShowCase’

    dotnet add ShowCase/ShowCase.csproj reference StringLibrary/StringLibrary.csproj

    Run ‘ShowCase’

    dotnet run --project ShowCase/ShowCase.csproj AB ab

    Unit Testing

    Create the unit test project and add a reference

    dotnet new mstest -o StringLibraryTest
    dotnet add StringLibraryTest/StringLibraryTest.csproj reference StringLibrary/StringLibrary.csproj

    Run the unit test

    dotnet test StringLibraryTest/StringLibraryTest.csproj

    Including the .dll directly

    Add the following to your project’s .csproj file. Change the StringLibrary project name as necessary.

      <ItemGroup>
        <Reference Include="StringLibrary">
          <HintPath>./lib/StringLibrary.dll</HintPath>
        </Reference>
      </ItemGroup>

    Installing C# on Ubuntu Linux

  • Background
  • Install the SDK
  • Install the Runtime
  • Create an Application
  • External Reading
  • Background

    The .NET framework was main implementation of the open specification, Common Language Infrastructure (CLI) which is platform independent target for high level languages.

    The Common Language Runtime (CLR) is synonymous to Java’s JRE. Compilers target the CLR and emit managed code. This code permits cross-language integration and exception handling.

    The .NET framework was cross-platform by design only, not on implementation. Until about 2012, the .NET Runtime (CLR) was for Windows, and the .Net Framework was Windows-centric. There was limited cross-platform support from the Mono project, being unsupported, it lagged behind the official releases.

    In June of 2016, Microsoft released .NET Core. This was a port of the CLR to Linux with a rewriting of the framework from the ground up. Version 2.0 was released in 2017, and version 3.0 in 2019. This numbering system conflicted with the Windows proprietary .NET framework numbering. As such, version 4 was skipped and .NET 5.0 was released in 2020 followed by .NET 6.0 in 2021.

    As of version 5.0, the old .NET Framework was given “legacy support” status, and .NET Core was renamed to just .NET. This is why the confusion really gets bad.

    In summary, .NET Framework had versions 1.0, 1.1, 2.0, 3.0, 3.5, and 4.0-4.8. It will never have a 5.0 version. While .NET Core had versions 1.0, 2.0, 2.1, 3.0, and 3.1. Then it was renamed, to just .NET 5.

    Future versions of .NET will provide LTS for even-numbered versions. New versions release every November.

    This image is licensed under the Creative Commons Attribution-Share Alike 4.0 International license.

    Source https://en.wikipedia.org/wiki/File:Overview_of_the_Common_Language_Infrastructure_2015.svg

    Install the SDK

    source

    The .NET SDK allows you to develop apps with .NET. If you install the .NET SDK, you don’t need to install the runtime.

    sudo apt-get update
    sudo apt-get install -y dotnet6

    Install the runtime (optional)

    The ASP.NET Core Runtime allows you to run apps that were made with .NET that didn’t provide the runtime.

    sudo apt-get update
    sudo apt-get install -y aspnetcore-runtime-6.0

    You can install the .NET Runtime, which doesn’t include ASP.NET Core.

    sudo apt-get update
    sudo apt-get install -y dotnet-runtime-6.0

    Create an application

    Create a new .NET application called “HelloWorld”

    1. Open your terminal
    2. Create and enter a new directory called “HelloWorld”
    3. Use the “dotnet” command to create a new application.
    dotnet new console --framework net6.0

    In your IDE, replace the contents of Program.cs with the following code:

    namespace HelloWorld {
        class Program {
            static void Main(string[] args) {
                Console.WriteLine("Hello World!");
            }
        }
    }

    Alternatively you can create a new console app with the directory structure by including the output flag (-o).

    dotnet new console -o HelloWorld

    External Reading

    Create a Class Library