• Cvars? How to implement it?
    8 replies, posted
So I wanna have cvars in my C++ engine, but then some problems arise: [list] [*] You can't see variable names as they are changed to asm, I assume you have to use std::map with a string as a key (that'll be cvar name), and then use something like this to add them: [code]void addCvar(const int &variable, std::string name);[/code] [*] Should the cvars be pointers to other variables in my code, or should other code access cvars in my cvar class? [*] If they are pointers, I'd like to have only 1 std::map, so the pointer must be void*, but then, since I wanna have different types of cvars, how do I know what type of variable the pointer is pointing to? [*] If they are stored in cvar class, wouldn't the log(n) std::map searching for cvars be too slow, since they will be searched often? While if they were pointers they would be only have to be searched while changing them. [*] And then, since I don't know variable type, what should I do when user types cv_someinteger="asd" or cv_somestring=5? [*] I've seen open source cvar project somewhere but I can't find it anymore, does anyone have a link? [/list] Facepunch coders! Help!
[QUOTE=Ritave;19273525]Should the cvars be pointers to other variables in my code, or should other code access cvars in my cvar class?[/QUOTE] Both work. [QUOTE=Ritave;19273525]If they are pointers, I'd like to have only 1 std::map, so the pointer must be void*, but then, since I wanna have different types of cvars, how do I know what type of variable the pointer is pointing to?[/QUOTE] You just need an integer telling you, of which type the void* is. If you are okay with using Boost, look up boost::variant and boost::any. [QUOTE=Ritave;19273525]If they are stored in cvar class, wouldn't the log(n) std::map searching for cvars be too slow, since they will be searched often? While if they were pointers they would be only have to be searched while changing them.[/QUOTE] I don't understand this. But you can sort your container or quicker searches :) [QUOTE=Ritave;19273525]And then, since I don't know variable type, what should I do when user types cv_someinteger="asd" or cv_somestring=5?[/QUOTE] Tell the user that he can't do it or just assign default value, e.g. sv_someinterger would be 0 if you wanna get an integer out of it. The second one would be okay, it's just "5". [QUOTE=Ritave;19273525]I've seen open source cvar project somewhere but I can't find it anymore, does anyone have a link?[/QUOTE] The Quake 3-code is GPL'ed.
[cpp] #pragma once #include "Bootil/Bootil.h" namespace Bootil { namespace Console { class BOOTIL_EXPORT Variable { public: typedef stdext::hash_map< BString, Variable* > List; static Variable* Find( BString strName ); static int FindInt( BString strName ); static float FindFloat( BString strName ); static BString FindString( BString strName ); static Variable::List& GetList(); public: Variable( BString strName, BString strDefaultValue, int iFlags = 0 ); ~Variable(); bool GetBool(); BString GetString(); float GetFloat(); int GetInt(); template <typename T> void Set( const T& strIn ) { m_Value = Bootil::String::ToString( strIn ); } BString GetName( void ); BString GetDefaultValue( void ); int GetFlags(); private: BString m_Name; BString m_Value; BString m_DefaultValue; int m_iFlags; }; } } [/cpp] [cpp]// foundation.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #include "bootil/Console/Variable.h" namespace Bootil { namespace Console { Variable::List& Variable::GetList() { static Variable::List s_CVarList; return s_CVarList; } Variable::Variable( BString strName, BString strDefaultValue, int iFlags ) { m_Name = strName; m_Value = strDefaultValue; m_DefaultValue = strDefaultValue; m_iFlags = iFlags; BString strLower = strName; String::ToLower( strLower ); Assert( Find( strLower ) == NULL ); GetList()[ strLower ] = this; } Variable::~Variable() { GetList().erase( m_Name ); } bool Variable::GetBool() { return String::To::Int( m_Value ) != 0; } BString Variable::GetString() { return m_Value; } float Variable::GetFloat() { return String::To::Float( m_Value ); } int Variable::GetInt() { return String::To::Int( m_Value ); } BString Variable::GetName( void ) { return m_Name; } BString Variable::GetDefaultValue( void ) { return m_DefaultValue; } int Variable::GetFlags() { return m_iFlags; } Variable* Variable::Find( BString strName ) { BString strFind = strName; String::ToLower( strFind ); Variable::List::iterator i = GetList().find( strFind ); if ( i == GetList().end() ) return NULL; return i->second; } int Variable::FindInt( BString strName ) { Variable* v = Find( strName ); if (!v) return 0; return v->GetInt(); } float Variable::FindFloat( BString strName ) { Variable* v = Find( strName ); if (!v) return 0; return v->GetFloat(); } BString Variable::FindString( BString strName ) { Variable* v = Find( strName ); if (!v) return ""; return v->GetString(); } } }[/cpp] [cpp]Console::Variable m_sensitivity( "sensitivity", "8", 0 );[/cpp] [cpp]m_MouseInfo.Deltas.x = m_MouseAxisAccum.x * m_sensitivity.GetFloat();[/cpp]
Oh wow, quite genius, never would have fought that way! Oh and since I don't really know windows library, what is that BOOTIL_EXPORT?
Bootil is part of my engine, no need to worry about that.
With that method should I say store the CVar value into a local variable for a class (say, scale of an entity): [cpp]Entity() {this->m_Scale_local = m_Scale_console.GetFloat();}[/cpp] or just continually call m_Scale_console.GetFloat() in my update function?
In that case, I think it would be best to have a callback whenever the particular cvar is changed.
[QUOTE=garry;19273822][cpp] code[/cpp][/QUOTE] So I was thinking, why do you have the Find & Get functions, instead of FindInt("var") couldn't you just do Find("var").GetInt() or something?
Just for the hell of it I'll post my implementation of a console+convar system too. [code] Global ConsoleOutHook:Int = AllocHookId() Type Console Global ConVars:TMap = New TMap Global ConCommands:TMap=New TMap Global Lines:TList = New TList Global History:TList = New TList Function AddVar(name:String , v:ConVar) ConVars.Insert(name,v) EndFunction Function GetVar:ConVar(name:String) Return ConVar(ConVars.ValueForKey(name)) EndFunction Function AddCommand(name:String, v:ConCommand) ConCommands.Insert(name, v) End Function Function GetCommand:ConCommand(name:String) Return ConCommand(ConCommands.ValueForKey(name)) End Function Function WriteLine(line:String) RunHooks(ConsoleOutHook, line) Lines.AddLast(line) If Lines.Count()>128 Then Lines.RemoveFirst() EndIf Print line EndFunction Function ProcessInput(in:String, silent:Byte = False) If in.Length = 0 Then Return History.AddLast(in) Local a:String[] = in.Split(" ") If Not silent Then WriteLine(">> " + in) EndIf If a.Dimensions()[0] = 1 Then ' The user has entered a single command If ConCommands.Contains(in) Then ' A concommand with this name exists. GetCommand(in).Run(Null) Return EndIf If ConVars.Contains(in) then ' A variable with this name exists. Local v:ConVar = GetVar(in) WriteLine(in+": "+v.Description+" Value: "+v.GetString()+" (Def. "+v.Def+")") return endif endif If a.Dimensions()[0] > 1 Then If ConCommands.Contains(a[0]) Then ' A concommand with this name exists. GetCommand(a[0]).Run(Right(in, in.Length - (a[0].Length + 1)).Split(" ")) Return EndIf If ConVars.Contains(a[0]) Then ' A variable with this name exists. Local v:ConVar = GetVar(a[0]) v.SetString(Right(in, in.Length - (a[0].Length + 1))) 'WriteLine(a[0] + " set to " + Right(in, in.Length - (a[0].Length + 1))) Return endif endif WriteLine("Unknown command '"+in+"'.") EndFunction Function Clear(s:String[] = null) Lines.Clear() EndFunction EndType Type ConVar Field Description:String Field Value:String Field Def:String Method GetString:String() Return Value EndMethod Method GetByte:Byte() Return Byte(Value) EndMethod Method GetInt:Int() Return Int(Value) EndMethod Method GetFloat:Float() Return Float(Value) End Method Method SetInt(v:Int) Value=String(v) EndMethod Method SetFloat(v:Float) Value = String(v) EndMethod Method SetString(v:String) Value=v EndMethod Function Create:ConVar(name:String, Desc:String, def:String = "0") Local c:ConVar = New ConVar c.Description = Desc c.Value = def c.Def=def Console.AddVar(name , c) Return c EndFunction EndType Type ConCommand Field Func:Int(args:String[]) Method Run(args:String[]) Func(args) EndMethod Function Create:ConCommand(name:String, f:Int(args:String[])) Local c:ConCommand = New ConCommand c.Func = f Console.AddCommand(name, c) Return c End Function Function MapTo(a:String, b:String) Console.AddCommand(a, Console.GetCommand(b)) End Function End Type Function ExecFiles:Int(a:String[]) If Not a Then Console.WriteLine("Specify file(s) to run.") Else For Local s:String = EachIn a Local f:TStream = OpenStream("Resource/" + s) If f Then While Not f.Eof() Console.ProcessInput(f.ReadLine()) EndWhile Else Console.WriteLine("File " + s + " not found.") EndIf Next Return 0 End If End Function ConCommand.Create("exec", ExecFiles) [/code] There's some oddities here because of language limitations but it works good. You declare a variable: Global cc_wireframe:ConVar = convar.Create("r_wireframe", "Wireframe rendering", "0") Then you can access it: [code] If cc_wireframe.GetByte() Then glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) Else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) End If [/code] The concommands work differently: [code] Function PissBalls:Int(a:String[]) If a Then TestShader = LoadShader("Resource/Shader/" + a[0] + ".frag") End If End Function ConCommand.Create("shader", PissBalls) [/code] Still, works as I wanted and if I were going to port it to another language it'd work the same way. I also implemented key bindings with this system rather shittily if anyone cares: [code] ' Key bindings! Global BKEY_FORWARD:Int = 0 Global BKEY_BACKWARD:Int = 0 Global BKEY_LEFT:Int = 0 Global BKEY_RIGHT:Int = 0 Global KEYBINDS:TMap = New TMap Global Special_Keys:TMap = New TMap Special_Keys.Insert("tab", String(KEY_TAB)) Special_Keys.Insert("space", String(KEY_SPACE)) Special_Keys.Insert("return", String(KEY_RETURN)) Special_Keys.Insert("enter", String(KEY_ENTER)) Special_Keys.Insert("alt", String(KEY_LALT)) Special_Keys.Insert("ctrl", String(KEY_LCONTROL)) Special_Keys.Insert("shift", String(KEY_LSHIFT)) Special_Keys.Insert("backspace", String(KEY_BACKSPACE)) Special_Keys.Insert("ins", String(KEY_INSERT)) Special_Keys.Insert("insert", String(KEY_INSERT)) Special_Keys.Insert("del", String(KEY_DELETE)) Special_Keys.Insert("deletr", String(KEY_DELETE)) Special_Keys.Insert("home", String(KEY_HOME)) Special_Keys.Insert("end", String(KEY_END)) Special_Keys.Insert("left", String(KEY_LEFT)) Special_Keys.Insert("right", String(KEY_RIGHT)) Special_Keys.Insert("up", String(KEY_UP)) Special_Keys.Insert("down", String(KEY_DOWN)) Special_Keys.Insert("tilde", "96") Special_Keys.Insert("`", "96") Function BindKey:Int(a:String[]) If a And a.Dimensions()[0] > 1 Then Local keynum:Int If a[0].Length = 1 Then keynum = Asc(Upper(a[0])) ElseIf Special_Keys.Contains(Lower(a[0])) Then keynum = Int(String(Special_Keys.ValueForKey(Lower(a[0])))) Else console.WriteLine("Must bind a valid key!") Return 0 EndIf Select a[1] Case "forward" BKEY_FORWARD = keynum Case "backward" BKEY_BACKWARD = keynum Case "left" BKEY_LEFT = keynum Case "right" BKEY_RIGHT = keynum Default Local l:Int = a.Dimensions()[0] Local build:String = "" For Local i:Int = 1 To l - 1 build = build + a[i] + " " Next KEYBINDS.Insert(String(keynum), build) End Select Else Console.WriteLine("Format: bind <key> <command>") EndIf End Function concommand.Create("bind", BindKey) Function RunKeyBindings() For Local key:String = EachIn KEYBINDS.Keys() If KeyHit(Int(key)) Then Console.ProcessInput(String(KEYBINDS.ValueForKey(key)), True) End If Next End Function [/code]
Sorry, you need to Log In to post a reply to this thread.