SyntaxBomb - Indie Coders

Languages & Coding => Blitz Code Archives => Miscellaneous => Topic started by: BlitzBot on June 29, 2017, 00:28:43

Title: [bmx] Console by N [ 1+ years ago ]
Post by: BlitzBot on June 29, 2017, 00:28:43
Title : Console
Author : N
Posted : 1+ years ago

Description : This does not handle displaying a console.  That's your job.  What this is is a simple system for registering cvars (console variables, for those less inclined to think) and console commands (e.g., in Doom 3, "/vid_restart").

I've tried to document the functions relatively simply, although it's not using bbdoc since this isn't a module and I don't really care.  For a simple example, see this:
Import "console.bmx"

Function test%( n$, l$ )
    Print "Console printed: "+l
End Function

RegisterConCommand( "test", test )

CallConsole( "/test foobaz" )
CallConsole( "testing" )

Print GetConsoleLog()


There is a basic framework for handling script callbacks, but since I cannot predict what interpreter you will use or how it handles callbacks, I've left that out.  You would insert this pretty much at the very end of CallConsole, where it looks like I've put an example of how I do it. [/i]

Code :
Code (blitzmax) Select
SuperStrict

Import Brl.LinkedList
Import Brl.Map

Private

Global ConsoleMap:TMap = New TMap
Global ConsoleLog:TList = New TList

Public
' If a cvar is set, this hook is called
' useful for monitoring in-game/engine changes
Global CVarSetHookID% = AllocHookID( )
' If anything is written to the console log, this hook is called
' useful for sending console messages to the debuglog, for example
Global ConsolePrintHookID% = AllocHookID( )

Private
Type ICVar
    Field _name$        ' name
    Field _value$       ' current value
    Field _readonly%    ' read-only?
    Field _system%      ' system/important?
    Field _script%=2    ' set from the script?  2 = set by the engine, but not system, and can be modified by script, 1 = (now a?) script variable, 0 = not.
End Type

Type ICCmd
    Field _name$ ' Name
    Field _callback:Int( n$, l$ )=Null ' Engine callback pointer
    Field _scallback$="" ' Script callback name
    Field _script%=0 ' Is a script callback?
    Field _system%=0 ' Is a system/important callback?
End Type

' Not exaclty unicode-friendly thanks to Chr
Function Strip( s$ Var, p$, replaceWith$="" )
    Local q$ = s$
    s = ""
    For Local i:Int = 0 To q.Length-1
        Local c$ = Chr(q[i])
        If p.Find(c)>-1 Then c = replaceWith
        s :+ c
    Next
End Function

Public

' Register a console command/callback
' name is the name of the command, obviously
' cb is the function callback, null if script is true
' system specifies whether or not it is a core/system callback (e.g., important to the function of the entire system), true/false
' script specifies whether or not it uses the script callback, true/false
' sb is the script callback, for instances where an interpreter handles the callback
Function RegisterConCommand( name$, cb:Int( n$, cmd$ ), system%=0, script%=0, sb$="" )
    name = name.ToLower( )
    Strip(name,"/;'~q.,[]():~~`-{}|?<>*&^%$#@!+=~n~r~t ","")
    If name.Length = 0 Then Return

    Local value:Object = ConsoleMap.ValueForKey( name )
    Local c:ICCmd = ICCmd(value)

    If c Then
        If c._system Then
            ConPrint( "Cannot overwrite system console command" )
            Return
        EndIf
        If (cb = Null And script=0) Or (script=1 And sb.Length=0) Then
            ConsoleMap.Remove( name )
            Return
        EndIf
        c._callback = cb
        Return
    ElseIf Not cb Or value Then
        Return
    EndIf

    c = New ICCmd
    c._name = name
    c._callback = cb
    c._script = script
    c._system = system

    ConsoleMap.Insert( name, c )
End Function

' handles a call to the console
' messages that do not begin with / are interpreted as wanting to just write msg to the console log
Function CallConsole( msg$ )
    Strip(msg,"~n~r~t","")
    If msg[0] <> "/"[0] Then
        ConPrint( msg )
        Return
    EndIf
   
    Local sp:Int = msg.Find(" ")
    If sp = -1 Then
        sp = msg.Length
    ElseIf sp = 1 Then
        ConPrint( "Invalid console command" )
        Return
    EndIf

    Local name$ = msg[1..sp].Trim( ).ToLower( )
    Strip(name,"/;'~q.,[]():~~`-{}|?<>*&^%$#@!+=","")
    If name.Length = 0 Then
        ConPrint( "Invalid console command" )
        Return
    EndIf

    Local value:Object = ConsoleMap.ValueForKey( name )
    Local c:ICCmd = ICCmd(value)

    If c = Null Then
        If sp = msg.Length Then
            'ConPrint( FormatString("$1 = ~q$2~q", [name, GetCVar(name)]) )
            ConPrint( name+" = ~q"+GetCVar(name)+"~q" )
        Else
            Local val$ = msg[sp..].Trim( )
            'ConPrint( FormatString("set $1 ~q$2~q", [name, val]) )
            SetCvar( name, val )
            ConPrint( name+" = ~q"+GetCVar(name)+"~q" )
        EndIf
        Return
    ElseIf c._script = 0 Then
        c._callback( name, msg[sp..].Trim( ) )
    Else
        Rem
        IScript.PushString( name )
        IScript.PushString( msg[sp..].Trim( ) )
        IScript.Call( c._scallback, 2, 0 )
        EndRem
        ' You'll have to insert how you handle script callbacks here.  I'm not responsible for it.
    EndIf
End Function

' gets a cvar
' n is the name of the cvar, d is a default value to give the cvar if it does not exist
' passing null to d will not create a cvar in the event that n doesn't exist
Function GetCVar$( n$, d$=Null )
    Strip(n, "/;'~q.,[]():~~`-{}|?<>*&^%$#@!+=~n~r~t ", "")
    n = n.ToLower( ).Trim( )

    Local value:Object = ConsoleMap.ValueForKey( n )
    Local c:ICvar = ICVar(value)

    If Not c And d.Length>0 And Not value And d Then
        SetCVar( n, d )
        Return GetCVar(n)
    ElseIf Not c
        Return ""
    Else
        Return c._value
    EndIf
End Function

' n is the name of the cvar
' v is the value- if the cvar already exists and v is null, the cvar is deleted
' readonly specifies whether or not calls to the console can modify the cvar (via CallConsole)
' fromScript specifies whether or not SetCVar is being called from a script function
' + in the event that the cvar does not already exist and fromScript is true, the cvar is created as a script cvar
' + (this is relatively meaningless unless you implement a way to save cvars)
' system specifies whether or not the cvar is a system/core/important cvar, roughly the same as specifying read-only
' + except that read-only cvars would not be saved in something like autoexec.cfg
Function SetCVar( n$, v$, readonly%=-1, fromScript%=-1, system%=-1 )
    n = n.ToLower( ).Replace("","").Trim( )
    Strip(n, "/;'~q.,[]():~~`-{}|?<>*&^%$#@!+=~n~r~t ", "")

    Local value:Object = ConsoleMap.ValueForKey( n )
    Local c:ICvar = ICVar(value)

    If Not c And Not value And v Then
        c = New ICVar
        c._name = n
        ConsoleMap.Insert( n, c )
    ElseIf Not c Then
        Return
    ElseIf Not v And c Then
        ConsoleMap.Remove( n )
        Return
    EndIf

    If c._readonly And fromScript Then
        ConPrint( n+" is a read-only cvar and cannot be overwritten" )
        Return
    EndIf
   
    Strip(v, "~t~n~r~0", "")
    c._value = v
    If readonly<>-1 Then c._readonly = readonly
    If system<>-1 Then c._system = system
    If c._script = 2 And fromScript <> -1 Then c._script = fromScript

    RunHooks( CVarSetHookID, [c._name,c._value] )
End Function

' deletes non-system cvars if force is zero, deletes all cvars if force is true
Function FlushCVars( force%=0 )
    For Local i:ICvar = EachIn ConsoleMap.Values()
        If Not i._system Or force Then ConsoleMap.Remove( i._name )
    Next
End Function

' deletes non-system commands if force is zero, deletes all commands if force is true
Function FlushConCommands( force%=0 )
    For Local i:ICvar = EachIn ConsoleMap.Values()
        If Not i._system Or force Then ConsoleMap.Remove( i._name )
    Next
End Function

' clears the console log (messages logged by ConPrint
Function ClearConsole( )
    ConsoleLog.Clear( )
End Function

' retrieves the console log as a string (useful for writing the log to a file)
Function GetConsoleLog$( )
    Local s$ = ""
    For Local i$ = EachIn ConsoleLog
        s :+ i + "~n"
    Next
    Return s.Trim()
End Function

' retrieves the console log in reverse as an array of strings (useful for displaying the console)
Function ReverseConsoleArray$[]( )
    Local obj:Object[] = (ConsoleLog.Reversed( ).ToArray( ))
    Local arr:String[obj.Length]
    For Local i:Int = 0 To arr.Length-1
        arr[i] = obj[i].ToString()
    Next
    Return arr
End Function

' writes a message to the console log
Function ConPrint( s$ )
    Strip( s, "~r~t", "")
    Local sp$[] = s.Split("~n")
    Local i%
    For i = sp.Length-1 To 0 Step -1
        ConsoleLog.AddLast( sp[i] )
    Next
    RunHooks( ConsolePrintHookID, s )
End Function

' displays all cvars and their values in the console
Function ListCVars( )
    For Local i:ICVar = EachIn ConsoleMap.Values( )
        ConPrint( "~q"+i._name+"~q = ~q"+i._value+"~q" )
    Next
End Function

' lists all console commands in the console
Function ListCommands( )
    For Local i:ICCmd = EachIn ConsoleMap.Values( )
        ConPrint( "~q"+i._name+"~q" )
    Next
End Function

' resets the console to an initial state
Function ResetConsole( )
    FlushConCommands( True )
    FlushCVars( True )
    RegisterConCommand( "listcommands", clistCommands, 1 )
    RegisterConCommand( "listcvars", clistCvars, 1 )
    RegisterConCommand( "clear", cclearConsole, 1 )
    ClearConsole( )
End Function

Private

Function clistCommands%( n$, s$ )
    ListCommands( )
End Function

Function clistCvars%( n$, s$ )
    ListCvars( )
End Function

Function cclearConsole%( n$, s$ )
    ClearConsole( )
End Function

ResetConsole()


Comments : none...