Created addressed_data_buffer as a universal way for systems to send data over various output devices. Cleaned up old SACN code, removed dmx as its not needed in its own right anymore.

This commit is contained in:
PS 2020-10-01 15:30:24 -07:00
parent 01a20f41e7
commit 78d44b9348
10 changed files with 247 additions and 352 deletions

View File

@ -1,57 +0,0 @@
//
// File: DMX_H
// Author: Peter Slattery
// Creation Date: 2020-01-01
//
#ifndef DMX_H
struct dmx_buffer
{
s32 Universe;
u8* Base;
s32 TotalSize;
s32 HeaderSize;
};
struct dmx_buffer_list
{
dmx_buffer Buffer;
dmx_buffer_list* Next;
};
internal dmx_buffer_list*
DMXBufferListGetTail (dmx_buffer_list* List)
{
dmx_buffer_list* Result = 0;
if (List->Next == 0)
{
Result = List;
}
else
{
Result = DMXBufferListGetTail(List->Next);
}
return Result;
}
internal dmx_buffer_list*
DMXBufferListAppend (dmx_buffer_list* AppendTo, dmx_buffer_list* Append)
{
dmx_buffer_list* Result = 0;
if (AppendTo)
{
dmx_buffer_list* Tail = DMXBufferListGetTail(AppendTo);
Tail->Next = Append;
Result = AppendTo;
}
else
{
Result = Append;
}
return Result;
}
#define DMX_H
#endif // DMX_H

View File

@ -0,0 +1,78 @@
//
// File: foldhaus_addressed_data.h
// Author: Peter Slattery
// Creation Date: 2020-10-01
//
// addressed_data_buffer is a generic buffer of data that also contains information
// regarding how it should be sent to a sculpture.
// This decouples the encoding step from the sending step.
//
#ifndef FOLDHAUS_ADDRESSED_DATA_H
enum data_buffer_address_type
{
AddressType_NetworkIP,
AddressType_Invalid,
};
struct addressed_data_buffer
{
u8* Memory;
u32 MemorySize;
data_buffer_address_type AddressType;
// IP Address
u32 V4SendAddress;
u32 SendPort;
addressed_data_buffer* Next;
};
struct addressed_data_buffer_list
{
addressed_data_buffer* Root;
addressed_data_buffer* Head;
};
internal addressed_data_buffer*
AddressedDataBufferList_Push(addressed_data_buffer_list* List, u32 BufferSize, gs_memory_arena* Storage)
{
addressed_data_buffer* Result = PushStruct(Storage, addressed_data_buffer);
*Result = {0};
Result->MemorySize = BufferSize;
Result->Memory = PushArray(Storage, u8, Result->MemorySize);
SLLPushOrInit(List->Root, List->Head, Result);
return Result;
}
internal void
AddressedDataBuffer_SetNetworkAddress(addressed_data_buffer* Buffer, u32 V4SendAddress, u32 SendPort)
{
Buffer->AddressType = AddressType_NetworkIP;
Buffer->V4SendAddress = V4SendAddress;
Buffer->SendPort = SendPort;
}
internal void
AddressedDataBuffer_Send(addressed_data_buffer Buffer, platform_socket_handle SendSocket, context Context)
{
u32 V4SendAddress = Buffer.V4SendAddress;
Context.PlatformSendTo(SendSocket, Buffer.V4SendAddress, Buffer.SendPort, (const char*)Buffer.Memory, Buffer.MemorySize, 0);
}
internal void
AddressedDataBufferList_SendAll(addressed_data_buffer_list OutputData, platform_socket_handle SendSocket, context Context)
{
for (addressed_data_buffer* BufferAt = OutputData.Root;
BufferAt != 0;
BufferAt = BufferAt->Next)
{
AddressedDataBuffer_Send(*BufferAt, SendSocket, Context);
}
}
#define FOLDHAUS_ADDRESSED_DATA_H
#endif // FOLDHAUS_ADDRESSED_DATA_H

View File

@ -90,5 +90,7 @@ struct assembly_array
assembly* Values;
};
internal led_buffer* LedSystemGetBuffer(led_system* System, u32 Index);
#define FOLDHAUS_ASSEMBLY_H
#endif // FOLDHAUS_ASSEMBLY_H

View File

@ -343,6 +343,52 @@ SACNGetUniverseSendAddress(s32 Universe)
return V4Address;
}
internal void
SACN_FillBufferWithLeds(u8* BufferStart, u32 BufferSize, v2_strip Strip, led_buffer LedBuffer)
{
u8* DestChannel = BufferStart;
for (u32 i = 0; i < Strip.LedCount; i++)
{
u32 LedIndex = Strip.LedLUT[i];
pixel Color = LedBuffer.Colors[LedIndex];
DestChannel[0] = Color.R;
DestChannel[1] = Color.G;
DestChannel[2] = Color.B;
DestChannel += 3;
}
}
internal void
SACN_BuildOutputData(streaming_acn* SACN, addressed_data_buffer_list* Output, assembly_array Assemblies, led_system* LedSystem, gs_memory_arena* OutputStorage)
{
SACNUpdateSequence(SACN);
// TODO(pjs): 512 is a magic number - make it a constant?
s32 BufferHeaderSize = STREAM_HEADER_SIZE;
s32 BufferBodySize = 512;
s32 BufferSize = BufferHeaderSize + BufferBodySize;
for (u32 AssemblyIdx = 0; AssemblyIdx < Assemblies.Count; AssemblyIdx++)
{
assembly Assembly = Assemblies.Values[AssemblyIdx];
led_buffer* LedBuffer = LedSystemGetBuffer(LedSystem, Assembly.LedBufferIndex);
for (u32 StripIdx = 0; StripIdx < Assembly.StripCount; StripIdx++)
{
v2_strip StripAt = Assembly.Strips[StripIdx];
u32 V4SendAddress = SACNGetUniverseSendAddress(StripAt.StartUniverse);
u32 SendPort = DEFAULT_STREAMING_ACN_PORT;
addressed_data_buffer* Data = AddressedDataBufferList_Push(Output, BufferSize, OutputStorage);
AddressedDataBuffer_SetNetworkAddress(Data, V4SendAddress, SendPort);
SACNPrepareBufferHeader(StripAt.StartUniverse, Data->Memory, Data->MemorySize, BufferHeaderSize, *SACN);
SACN_FillBufferWithLeds(Data->Memory + BufferHeaderSize, BufferBodySize, StripAt, *LedBuffer);
}
}
}
#define SACN_H
#endif // SACN_H

View File

@ -8,35 +8,6 @@
#include "foldhaus_platform.h"
#include "foldhaus_app.h"
struct send_sacn_job_data
{
platform_socket_handle SendSocket;
platform_send_to* SendTo;
dmx_buffer_list* DMXBuffers;
};
internal void
SACNSendDMXBufferListJob (s32 ThreadID, void* JobData)
{
DEBUG_TRACK_FUNCTION;
send_sacn_job_data* Data = (send_sacn_job_data*)JobData;
platform_socket_handle SendSocket = Data->SendSocket;
dmx_buffer_list* DMXBufferAt = Data->DMXBuffers;
while (DMXBufferAt)
{
dmx_buffer Buffer = DMXBufferAt->Buffer;
u32 V4SendAddress = SACNGetUniverseSendAddress(Buffer.Universe);
Data->SendTo(SendSocket, V4SendAddress, DEFAULT_STREAMING_ACN_PORT, (const char*)Buffer.Base, Buffer.TotalSize, 0);
DMXBufferAt = DMXBufferAt->Next;
}
}
////////////////////////////////////////////////////////////////////////
RELOAD_STATIC_DATA(ReloadStaticData)
@ -246,56 +217,6 @@ HandleInput (app_state* State, rect2 WindowBounds, input_queue InputQueue, mouse
ClearCommandQueue(&State->CommandQueue);
}
internal dmx_buffer_list*
CreateDMXBuffers(assembly Assembly, led_system* LedSystem, s32 BufferHeaderSize, gs_memory_arena* Arena)
{
DEBUG_TRACK_FUNCTION;
led_buffer* LedBuffer = LedSystemGetBuffer(LedSystem, Assembly.LedBufferIndex);
dmx_buffer_list* Result = 0;
dmx_buffer_list* Head = 0;
s32 BufferSize = BufferHeaderSize + 512;
for (u32 StripIndex = 0; StripIndex < Assembly.StripCount; StripIndex++)
{
v2_strip Strip = Assembly.Strips[StripIndex];
dmx_buffer_list* NewBuffer = PushStruct(Arena, dmx_buffer_list);
NewBuffer->Buffer.Universe = Strip.StartUniverse;
NewBuffer->Buffer.Base = PushArray(Arena, u8, BufferSize);
NewBuffer->Buffer.TotalSize = BufferSize;
NewBuffer->Buffer.HeaderSize = BufferHeaderSize;
NewBuffer->Next = 0;
// Append
if (!Result) {
Result = NewBuffer;
Head = Result;
}
Head->Next = NewBuffer;
Head = NewBuffer;
u8* DestChannel = Head->Buffer.Base + BufferHeaderSize;
for (u32 i = 0; i < Strip.LedCount; i++)
{
u32 LedIndex = Strip.LedLUT[i];
pixel Color = LedBuffer->Colors[LedIndex];
DestChannel[0] = Color.R;
DestChannel[1] = Color.G;
DestChannel[2] = Color.B;
DestChannel += 3;
}
}
return Result;
}
UPDATE_AND_RENDER(UpdateAndRender)
{
DEBUG_TRACK_FUNCTION;
@ -420,44 +341,35 @@ UPDATE_AND_RENDER(UpdateAndRender)
}
}
// Skipped for performance at the moment
#if 0
dmx_buffer_list* DMXBuffers = 0;
for (u32 i = 0; i < State->Assemblies.Count; i++)
addressed_data_buffer_list OutputData = {0};
switch (State->NetworkProtocol)
{
assembly* Assembly = &State->Assemblies.Values[i];
dmx_buffer_list* NewDMXBuffers = CreateDMXBuffers(*Assembly, &State->LedSystem, STREAM_HEADER_SIZE, &State->Transient);
DMXBuffers = DMXBufferListAppend(DMXBuffers, NewDMXBuffers);
case NetworkProtocol_SACN:
{
SACN_BuildOutputData(&State->SACN, &OutputData, State->Assemblies, &State->LedSystem, State->Transient);
}break;
case NetworkProtocol_UART:
{
//UART_BuildOutputData(&OutputData, State, State->Transient);
}break;
case NetworkProtocol_ArtNet:
InvalidDefaultCase;
}
//DEBUG_IF(GlobalDebugServices->Interface.SendSACNData)
if (0)
{
switch (State->NetworkProtocol)
{
case NetworkProtocol_SACN:
{
SACNUpdateSequence(&State->SACN);
dmx_buffer_list* CurrentDMXBuffer = DMXBuffers;
while (CurrentDMXBuffer)
{
dmx_buffer Buffer = CurrentDMXBuffer->Buffer;
SACNPrepareBufferHeader(Buffer.Universe, Buffer.Base, Buffer.TotalSize, Buffer.HeaderSize, State->SACN);
CurrentDMXBuffer = CurrentDMXBuffer->Next;
}
send_sacn_job_data* Job = PushStruct(&State->Transient, send_sacn_job_data);
Job->SendSocket = State->SACN.SendSocket;
Job->SendTo = Context->PlatformSendTo;
Job->DMXBuffers = DMXBuffers;
Context->GeneralWorkQueue->PushWorkOnQueue(Context->GeneralWorkQueue, SACNSendDMXBufferListJob, Job, "SACN Send Data Job");
}break;
InvalidDefaultCase;
}
// TODO(pjs): This should happen on another thread
AddressedDataBufferList_SendAll(OutputData, State->SACN.SendSocket, *Context);
/*
Saved this lien as an example of pushing onto a queue
Context->GeneralWorkQueue->PushWorkOnQueue(Context->GeneralWorkQueue, SACNSendDMXBufferListJob, Job, "SACN Send Data Job");
*/
}
#endif
// Skipped for performance at the moment

View File

@ -14,12 +14,13 @@
#include "interface.h"
#include "engine/foldhaus_network_ordering.h"
#include "engine/dmx/dmx.h"
#include "engine/sacn/sacn.h"
#include "engine/foldhaus_assembly.h"
#include "engine/assembly_parser.cpp"
#include "engine/foldhaus_addressed_data.h"
#include "engine/sacn/sacn.h"
typedef struct app_state app_state;
// TODO(Peter): something we can do later is to remove all reliance on app_state and context
@ -36,6 +37,7 @@ enum network_protocol
{
NetworkProtocol_SACN,
NetworkProtocol_ArtNet,
NetworkProtocol_UART,
NetworkProtocol_Count,
};

View File

@ -18,6 +18,7 @@
#include "win32_foldhaus_memory.h"
#include "win32_foldhaus_dll.h"
#include "win32_foldhaus_timing.h"
#include "win32_serial.h"
#include "../foldhaus_renderer.cpp"
@ -951,6 +952,10 @@ v4 ToScreen(v4 P, rect2 WindowBounds)
return Result;
}
//
// Serial
//
int WINAPI
WinMain (
HINSTANCE HInstance,
@ -961,168 +966,18 @@ WinMain (
{
gs_thread_context ThreadContext = Win32CreateThreadContext();
{
m44 Before = m44{
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15
};
m44 After = M44Transpose(Before);
OutputDebugStringA("Before:\n");
PrintMatrix(Before, ThreadContext);
OutputDebugStringA("\n\n");
OutputDebugStringA("After:\n");
PrintMatrix(After, ThreadContext);
OutputDebugStringA("\n\n");
}
{
v4 Before = {1, 2, 3, 4};
m44 Transform = {};
for (u32 i = 0; i < 16; i++)
{
Transform.Array[i] = i + 1;
}
v4 After = Transform * Before;
Assert(V4Mag(After - v4{30, 70, 110, 150}) < .00000001f);
gs_const_string TestString = ConstString("Hello World!\nTesting\n");
HANDLE SerialPortHandle = Win32SerialPort_Open("COM5");
Win32SerialPort_SetState(SerialPortHandle, 9600, 8, 0, 1);
Win32SerialPort_Write(SerialPortHandle, StringToData(TestString));
Win32SerialPort_Close(SerialPortHandle);
}
{ // Translation
v4 Before = {0, 0, 0, 1};
m44 Translation = M44Translation(v4{5, 5, 5, 0});
v4 After = Translation * Before;
Assert((After == v4{5, 5, 5, 1}));
}
{ // X Rotation
v4 Before = {0, 5, 0, 1};
m44 Forward = M44RotationX(HalfPiR32);
m44 Backward = M44RotationX(-HalfPiR32);
v4 After = Forward * Before;
Assert(V4Mag(After - v4{0, 0, -5, 1}) < .000001f);
After = Backward * Before;
Assert(V4Mag(After - v4{0, 0, 5, 1}) < .000001f);
}
{ // Y Rotation
v4 Before = {5, 0, 0, 1};
m44 Forward = M44RotationY(HalfPiR32);
m44 Backward = M44RotationY(-HalfPiR32);
v4 After = Forward * Before;
Assert(V4Mag(After - v4{0, 0, -5, 1}) < .000001f);
After = Backward * Before;
Assert(V4Mag(After - v4{0, 0, 5, 1}) < .000001f);
}
{ // Z Rotation
v4 Before = {0, 5, 0, 1};
m44 Forward = M44RotationZ(HalfPiR32);
m44 Backward = M44RotationZ(-HalfPiR32);
v4 After = Forward * Before;
Assert(V4Mag(After - v4{-5, 0, 0, 1}) < .000001f);
After = Backward * Before;
Assert(V4Mag(After - v4{5, 0, 0, 1}) < .000001f);
}
{ // Combined X Rotation
v4 Before = {0, 5, 0, 1};
m44 Forward = M44Rotation(v3{HalfPiR32, 0, 0});
m44 Backward = M44Rotation(v3{-HalfPiR32, 0, 0});
v4 After = Forward * Before;
Assert(V4Mag(After - v4{0, 0, -5, 1}) < .000001f);
After = Backward * Before;
Assert(V4Mag(After - v4{0, 0, 5, 1}) < .000001f);
}
{ // Combined Y Rotation
v4 Before = {5, 0, 0, 1};
m44 Forward = M44Rotation(v3{0, HalfPiR32, 0});
m44 Backward = M44Rotation(v3{0, -HalfPiR32, 0});
v4 After = Forward * Before;
Assert(V4Mag(After - v4{0, 0, -5, 1}) < .000001f);
After = Backward * Before;
Assert(V4Mag(After - v4{0, 0, 5, 1}) < .000001f);
}
{ // Combined Z Rotation
v4 Before = {0, 5, 0, 1};
m44 Forward = M44Rotation(v3{0, 0, HalfPiR32});
m44 Backward = M44Rotation(v3{0, 0, -HalfPiR32});
v4 After = Forward * Before;
Assert(V4Mag(After - v4{-5, 0, 0, 1}) < .000001f);
After = Backward * Before;
Assert(V4Mag(After - v4{5, 0, 0, 1}) < .000001f);
}
{ // Translate then Rotate
v4 Before = v4{0, 0, 0, 1};
m44 Translation = M44Translation(v4{5, 0, 0, 0});
m44 Rotation = M44Rotation(v3{0, 0, HalfPiR32});
m44 Composite = Rotation * Translation;
v4 Inbetween = Translation * Before;
v4 After = Rotation * Inbetween;
Assert(V4Mag(After - v4{0, 5, 0, 1}) < .000001f);
After = Composite * Before;
Assert(V4Mag(After - v4{0, 5, 0, 1}) < .000001f);
}
{ // Two translations
v4 Before = v4{0, 0, 0, 1};
m44 TranslationA = M44Translation(v4{5, 0, 0, 0});
m44 TranslationB = M44Translation(v4{0, 5, 0, 0});
v4 After = TranslationB * TranslationA * Before;
Assert(V4Mag(After - v4{5, 5, 0, 1}) < .000001f);
}
{ // Perspective Transform
rect2 WindowBounds = rect2{
v2{0, 0},
v2{1440.0f, 768.0f},
};
m44 Matrix = M44Translation(v4{0, 0, -200, 0}) * M44Rotation(v3{0, DegToRadR32(45), 0});
m44 Projection = M44ProjectionPerspective(45, RectAspectRatio(WindowBounds), 0.1f, 500);
r32 Rad = 25;
v4 P0 = Matrix * v4{-Rad, -Rad, 0, 1};
v4 P1 = Matrix * v4{Rad, -Rad, 0, 1};
v4 P2 = Matrix * v4{Rad, Rad, 0, 1};
v4 P3 = Matrix * v4{-Rad, Rad, 0, 1};
v4 P0P = Projection * P0;
v4 P1P = Projection * P1;
v4 P2P = Projection * P2;
v4 P3P = Projection * P3;
v4 P0PD = PerspectiveDivide(P0P);
v4 P1PD = PerspectiveDivide(P1P);
v4 P2PD = PerspectiveDivide(P2P);
v4 P3PD = PerspectiveDivide(P3P);
v4 P0S = ToScreen(P0PD, WindowBounds);
P0S.w = 1;
v4 P1S = ToScreen(P1PD, WindowBounds);
P1S.w = 1;
v4 P2S = ToScreen(P2PD, WindowBounds);
P2S.w = 1;
v4 P3S = ToScreen(P3PD, WindowBounds);
P3S.w = 1;
Assert(V4Mag(P0S - v4{630.11401, 256.88202, 0.99930286, 1}) < 0.00001f);
Assert(V4Mag(P1S - v4{795.28662, 277.52859, 0.99948108, 1}) < 0.00001f);
Assert(V4Mag(P2S - v4{795.28662, 490.47144, 0.99948108, 1}) < 0.00001f);
Assert(V4Mag(P3S - v4{630.11401, 511.11798, 0.99930286, 1}) < 0.00001f);
//PushRenderQuad2D(RenderBuffer, P0S.xy, P1S.xy, P2S.xy, P3S.xy, WhiteV4);
}
MainWindow = Win32CreateWindow (HInstance, "Foldhaus", 1440, 768, HandleWindowEvents);
Win32UpdateWindowDimension(&MainWindow);

View File

@ -0,0 +1,81 @@
//
// File: win32_serial.h
// Author: Peter Slattery
// Creation Date: 2020-10-01
//
#ifndef WIN32_SERIAL_H
DCB
Win32SerialPort_GetState(HANDLE ComPortHandle)
{
DCB ControlSettings = {0};
ZeroStruct(&ControlSettings);
ControlSettings.DCBlength = sizeof(ControlSettings);
bool Success = GetCommState(ComPortHandle, &ControlSettings);
Assert(Success);
return ControlSettings;
}
void
Win32SerialPort_SetState(HANDLE ComPortHandle, u32 BaudRate, u8 ByteSize, u8 Parity, u8 StopBits)
{
DCB ControlSettings = Win32SerialPort_GetState(ComPortHandle);
//PrintCommState(ControlSettings);
// TODO(pjs): Validate BaudRate - There's only certain rates that are valid right?
ControlSettings.BaudRate = BaudRate;
ControlSettings.ByteSize = ByteSize;
ControlSettings.Parity = Parity;
ControlSettings.StopBits = StopBits;
bool Success = SetCommState(ComPortHandle, &ControlSettings);
}
HANDLE
Win32SerialPort_Open(char* PortName)
{
HANDLE ComPortHandle = CreateFile(PortName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, // Default Security Attr
OPEN_EXISTING,
0, // Not overlapped I/O
NULL);
if (ComPortHandle == INVALID_HANDLE_VALUE)
{
// Error
s32 Error = GetLastError();
InvalidCodePath;
}
return ComPortHandle;
}
void
Win32SerialPort_Close(HANDLE PortHandle)
{
CloseHandle(PortHandle);
}
void
Win32SerialPort_Write(HANDLE PortHandle, gs_data Buffer)
{
DWORD BytesWritten = 0;
if (WriteFile(PortHandle, Buffer.Memory, Buffer.Size, &BytesWritten, NULL))
{
if (BytesWritten != Buffer.Size)
{
OutputDebugString("Error: Entire buffer not written.\n");
}
}
else
{
OutputDebugStringA("Error: Unable to write to port\n");
}
}
#define WIN32_SERIAL_H
#endif // WIN32_SERIAL_H

View File

@ -292,7 +292,7 @@ CopyMemory_(u8* From, u8* To, u64 Size)
#define StaticArrayLength(arr) sizeof(arr) / sizeof((arr)[0])
#define ZeroMemoryBlock(mem,size) ZeroMemory_((u8*)(mem), (size))
#define ZeroStruct(str) ZeroMemory_((str), sizeof(str))
#define ZeroStruct(str) ZeroMemory_((u8*)(str), sizeof(*str))
#define ZeroArray(arr, type, count) ZeroMemory_((u8*)(arr), sizeof(type) * (count))
#define CopyArray(from, to, type, count) CopyMemory_((u8*)(from), (u8*)(to), sizeof(type) * (count))

View File

@ -14,7 +14,6 @@ STREAM #1: 3D Overhaul
- leds always face camera
- Sculptures
- implicitly add a tag equal to the sculpture name to each strip of leds
- cache led vertex buffers
- custom sculpture update functions (for motion)
- editing sculpture files (change universe output)
@ -53,8 +52,6 @@ STREAM #1: 3D Overhaul
- saving projects
STRAM #4: Completeness
- Win32 Platform Layer
- Enumerate Directory Contents (you started this in win32_foldhaus_fileio.h)
- Platform Layer
- Mac Platform Layer
@ -85,27 +82,6 @@ Assembly -> SACN interface
BUGS
- Typing a period into a float value doesn't register. Problem here is that we arent' translating key presses into characters at the win32 layer. Need to do that.
Switch To Nodes
- BUG: Connect a solid color node to output then try and pick a color. I think temporary node buffers are
- overwriting other data when they get evaluated. Bigger solution is to create a system of working
- led buffers that get assigned to nodes as needed.
- gonna need to remove output node from the node lister
- delete nodes
- - Simplify node storage
- - investigate if node connections can be operated on separately from the node headers
- - UpdatedThisFrame can probably go into each connection
- - Allow insertion/deletion within connection table
- - Allow inerstion/deletion within header table
- - maintain free list of connections and headers
- separate node functionality from drawing the nodes
- - pull node position, size, etc out into a parallel data structure. after split names: node_header, interface_node
- - create separate files: foldhaus_node_engine, foldhaus_node_gui
- - store interface_nodes in binary tree
- - allow panning and zooming around the node canvas
- - Investigate why we're giving nodes bounds :NodesDontNeedToKnowTheirBounds
- selector node (has a list of connections that it can switch between)
- evaluation step (one node at a time)
Hardening
- Then we want to think about separating out mode render functions from mode update functions. Not sure its necessary but having something that operates like an update funciton but is called render is weird. Might want some sort of coroutine functionality in place, where modes can add and remove optional, parallel
update functions