Skip to content

Commit

Permalink
Merge pull request #46 from MuffinTastic/MuffinTastic/cvar-system-imp…
Browse files Browse the repository at this point in the history
…rovements

Console System Improvements
  • Loading branch information
xezno authored Jan 31, 2023
2 parents e9310b1 + 3b36366 commit b0e67a0
Show file tree
Hide file tree
Showing 15 changed files with 1,529 additions and 168 deletions.
32 changes: 32 additions & 0 deletions Source/Common/Console/CVarFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mocha.Common;

public enum CVarFlags
{
// Mirrors native

None = 0,

// If this isn't present, it's inherently assumed to be a variable
Command = 1 << 0,

// If this is present, it lives in managed space
Managed = 1 << 1,

// This cvar was created by the game, it should be wiped on hotload
Game = 1 << 2,

// Save this convar to cvars.json
Archive = 1 << 3,

Cheat = 1 << 4,

Temp = 1 << 5,

Replicated = 1 << 6,
}
61 changes: 61 additions & 0 deletions Source/Common/Console/ConCmd.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Mocha.Common;

public static class ConCmd
{
public abstract class BaseAttribute : Attribute
{
internal string? Name { get; init; }
internal CVarFlags Flags { get; init; }
internal string? Description { get; init; }

public BaseAttribute( string? name, CVarFlags flags, string? description )
{
Name = name;
Flags = flags;
Description = description;
}
}

// public class ServerAttribute
// public class ClientAttribute

public sealed class TestAttribute : BaseAttribute
{
public TestAttribute()
: base( null, CVarFlags.None, null )
{

}
public TestAttribute( string name )
: base( name, CVarFlags.None, null )
{

}

public TestAttribute( string name, string description )
: base( name, CVarFlags.None, description )
{

}
}

public sealed class CheatAttribute : BaseAttribute
{
public CheatAttribute()
: base( null, CVarFlags.Cheat, null )
{

}
public CheatAttribute( string name )
: base( name, CVarFlags.Cheat, null )
{

}

public CheatAttribute( string name, string description )
: base( name, CVarFlags.Cheat, description )
{

}
}
}
16 changes: 16 additions & 0 deletions Source/Common/Console/ConVar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Mocha.Common;

public static class ConVar
{
public abstract class BaseAttribute : Attribute
{
public required string Name { get; init; }
public required CVarFlags Flags { get; init; }
public required string Description { get; init; }
}

public class TestAttribute : Attribute
{

}
}
41 changes: 41 additions & 0 deletions Source/Common/Console/ConsoleDispatchInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Mocha.Common.Console;


[StructLayout( LayoutKind.Sequential )]
public struct ConCmdDispatchInfo
{
public IntPtr name;
public IntPtr data;
public int size;
}

[StructLayout( LayoutKind.Sequential )]
public struct StringCVarDispatchInfo
{
public IntPtr name;
public IntPtr oldValue;
public IntPtr newValue;
}

[StructLayout( LayoutKind.Sequential )]
public struct FloatCVarDispatchInfo
{
public IntPtr name;
public float oldValue;
public float newValue;
}

[StructLayout( LayoutKind.Sequential )]
public struct BoolCVarDispatchInfo
{
public IntPtr name;
public bool oldValue;
public bool newValue;
}
176 changes: 176 additions & 0 deletions Source/Common/Console/ConsoleSystem.Internal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Mocha.Common;

public static partial class ConsoleSystem
{
public static class Internal
{
internal struct ConCmdCallbackInfo
{
public required MethodInfo method;
public required ParameterInfo[] parameters;
}

internal static class ConVarCallbackStore<T>
{
public static Dictionary<string, Action<T, T>> Callbacks = new();
}

internal static List<string> s_items = new();

internal static Dictionary<string, ConCmdCallbackInfo> s_commandCallbacks = new();

internal static void RegisterCommand( string name, CVarFlags flags, string description, ConCmdCallbackInfo callbackInfo )
{
Glue.ConsoleSystem.RegisterCommand( name, flags, description );
s_commandCallbacks[name] = callbackInfo;
s_items.Add( name );
}

internal static void RegisterStringConVar( string name, string value, CVarFlags flags, string description, Action<string, string> callback )
{
Glue.ConsoleSystem.RegisterString( name, value, flags, description );
ConVarCallbackStore<string>.Callbacks[name] = callback;
s_items.Add( name );
}

internal static void RegisterFloatConVar( string name, float value, CVarFlags flags, string description, Action<float, float> callback )
{
Glue.ConsoleSystem.RegisterFloat( name, value, flags, description );
ConVarCallbackStore<float>.Callbacks[name] = callback;
s_items.Add( name );
}

internal static void RegisterBoolConVar( string name, bool value, CVarFlags flags, string description, Action<bool, bool> callback )
{
Glue.ConsoleSystem.RegisterBool( name, value, flags, description );
ConVarCallbackStore<bool>.Callbacks[name] = callback;
s_items.Add( name );
}

/// <summary>
/// Register all of the CVars in an assembly
/// </summary>
/// <param name="assembly">The assembly to grab from</param>
/// <param name="extraFlags">Extra flags to force each CVar to have. Used mainly for hotloaded assemblies</param>
public static void RegisterAssembly( Assembly assembly, CVarFlags extraFlags = CVarFlags.None )
{
if ( assembly is null )
return;

foreach ( Type type in assembly.GetTypes() )
{
foreach ( MethodInfo method in type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ) )
{
ConCmd.BaseAttribute? customAttribute = method.GetCustomAttribute<ConCmd.BaseAttribute>();
if ( customAttribute is not null )
{
var parameters = method.GetParameters();

var callbackInfo = new ConCmdCallbackInfo
{
method = method,
parameters = parameters
};

RegisterCommand( customAttribute.Name ?? method.Name, customAttribute.Flags | extraFlags, customAttribute.Description ?? "", callbackInfo );
}
}
}
}

public static void ClearGameCVars()
{
foreach ( var name in s_items.ToList() )
{
bool exists = Glue.ConsoleSystem.Exists( name );
CVarFlags flags = Glue.ConsoleSystem.GetFlags( name );

if ( exists && (flags & CVarFlags.Game) != 0 )
{
s_items.Remove( name );
s_commandCallbacks.Remove( name );
ConVarCallbackStore<string>.Callbacks.Remove( name );
ConVarCallbackStore<float>.Callbacks.Remove( name );
ConVarCallbackStore<bool>.Callbacks.Remove( name );

Glue.ConsoleSystem.Remove( name );
}
}
}

public static void DispatchCommand( string name, List<string> dispatchArguments )
{
if ( !s_commandCallbacks.TryGetValue( name, out ConCmdCallbackInfo callbackInfo ) )
return;

ParameterInfo[] callbackParameters = callbackInfo.parameters;

if ( callbackParameters.Length == 1 &&
callbackParameters[0].ParameterType == dispatchArguments.GetType() )
{
// All it takes is a List<string> so we can probably assume they want direct access to the arguments
callbackInfo.method.Invoke( null, new object[] { dispatchArguments } );
return;
}

object?[]? arguments = null;

if ( callbackParameters.Length > 0 )
{
//
// Softly convert arguments from strings to whatever the callback expects
//

arguments = new object[callbackParameters.Length];

for ( int i = 0; i < callbackParameters.Length; i++ )
{
object? value;

ParameterInfo parameter = callbackParameters[i];
Type parameterType = parameter.ParameterType;

if ( i < dispatchArguments.Count )
{
// Try to convert
if ( !dispatchArguments[i].TryConvert( parameterType, out value ) )
{
Log.Error( $"Error dispatching ConCmd '{name}': Couldn't convert '{dispatchArguments[i]}' to type {parameterType}" );
return;
}
}
else if ( parameter.HasDefaultValue )
{
// There weren't enough arguments passed in,
// but we have a default value, so use that instead
value = parameter.DefaultValue;
}
else
{
Log.Error( $"Error dispatching ConCmd '{name}': Not enough arguments - expected {callbackParameters.Length}, got {dispatchArguments.Count}" );
return;
}

arguments[i] = value;
}
}

callbackInfo.method.Invoke( null, arguments );
}

public static void DispatchConVarCallback<T>( string name, T oldValue, T newValue )
{
if ( !ConVarCallbackStore<T>.Callbacks.TryGetValue( name, out var callback ) )
return;

callback( oldValue, newValue );
}
}
}
43 changes: 43 additions & 0 deletions Source/Common/Console/ConsoleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Reflection;
using Mocha.Common.Console;

namespace Mocha.Common;

public static partial class ConsoleSystem
{
public static void Run( string command )
{
Glue.ConsoleSystem.Run( command );
}

public static float GetFloat( string name )
{
return Glue.ConsoleSystem.GetFloat( name );
}

public static bool GetBool( string name )
{
return Glue.ConsoleSystem.GetBool( name );
}

public static void Set( string name, string value )
{
Glue.ConsoleSystem.SetString( name, value );
}

public static void Set( string name, float value )
{
Glue.ConsoleSystem.SetFloat( name, value );
}

public static void Set( string name, bool value )
{
Glue.ConsoleSystem.SetBool( name, value );
}

public static void SetFromString( string name, string value )
{
Glue.ConsoleSystem.FromString( name, value );
}
}
Loading

0 comments on commit b0e67a0

Please sign in to comment.