Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUSHclient ➜ General ➜ MUSHclient source organization

MUSHclient source organization

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Sun 01 Apr 2007 11:54 PM (UTC)

Amended on Sun 08 Apr 2007 04:09 AM (UTC) by Nick Gammon

Message
With the imminent release of the MUSHclient source code, I thought it would be useful to write a bit of an explanation of how the source is organized.

The source consists of something like 400 .cpp and .h files, and it could be a bit overwhelming trying to work out which part holds the "important bits" without some sort of guideline.

When someone comes to me with a request for a new feature, or new script routine, there are some files I automatically go to initially, I will try to describe those below.

MUSHclient was written using MFC (Microsoft Foundation Class) libraries - a thorough understanding of those will be very helpful in understanding how many things work.

Also, because of the use of STL, it helps to understand that as well.

It was compiled with Microsoft Visual Studio Version 6.0 with Service Pack 6 installed.

A note on style

MUSHclient has been a work in progress since late 1995. My style has changed somewhat in that time, plus as the program complexity has increased some parts have become semi-redundant.

For example, there is code to return the name of the current world:


// world.worldname - returns the name of the current world

BSTR CMUSHclientDoc::WorldName() 
{
  CString strResult;

  strResult = m_mush_name;
  
  return strResult.AllocSysString();
}


However after a while it became obvious that there would be lots of little "get information" functions, and thus the GetInfo function was born. Now, you can use GetInfo (2) to achieve the same result. Thus, the earlier WorldName was only retained for backwards compatibility.

There are also some places where things which are similar are done in different ways (eg. Macros / Accelerators). These are examples of where a simpler method, which I initially used, has been replaced by a more powerful method.


Lists / maps / strings

There are extensive lists / vectors / maps in the code, as well as "string" classes. Initially when MUSHclient was developed I used the MFC versions of these. For example, a list of strings might be:


  CStringList m_strIncludeFileList;         // list of root level include files


More recently I started using STL (Standard Template Library), and where convenient, used the STL containers instead. For example:


  deque<string> m_sRecentLines; // for multi-line triggers


Iterating through an MFC list generally looks like this:


  for (POSITION pos = m_PluginList.GetHeadPosition();  // get first position
       pos;   // check if we are at the end yet
       )      
    {
    CPlugin * p = m_PluginList.GetNext (pos);  // get current, advance to next

    // do something with the plugin (*p) here

    }


However iterating through an STL container looks like this:


    for (map<string, string>::const_iterator iter = sometable.begin (); // get first position
         iter != sometable.end ();  // check if we are at the end yet
         iter++)                    // advance to next
           {
           const char * key = iter->first.c_str ();      // key of map
           const char * value = iter->second.c_str ();   // value associated with key
           }  // end of doing each one



Strings are generally stored in the MFC CString class, however some parts of the code now use the STL string class.

File naming conventions


  • Generally a file named like this "xxxxxDlg.cpp" is the implementation for a dialog box, and the corresponding "xxxxxDlg.h" file is the header file for that dialog.

    For example, SpellCheckDlg.cpp implements the CSpellCheckDlg class (note the "C" in front of the class name).

  • Files to do with MXP processing are prefixed mxpXXXX, for example: mxp_phases.cpp, mxpCloseAtomic.cpp etc.

  • Files for loading/saving the XML data (eg. world files) are named xmlXXXX, for example: xml_load_world.cpp, xml_save_world.cpp.

  • Plugin-related files are named PluginXXXX, eg. plugins.cpp, PluginWizard.cpp, PluginWizardSheet.cpp.

  • Various Lua-related files are named lua_XXXX, eg. lua_methods.cpp, lua_scripting.cpp, etc.






Major files



  • MUSHclient application (CMUSHclientApp):


    • MUSHclient.cpp
    • MUSHclient.h


    This implements application-wide code, particularly program startup, in the function:

    
    BOOL CMUSHclientApp::InitInstance()
    {
    // start up here
    }
    


    This does stuff like basic initialization, parsing command-line options, and opening worlds in the startup list.


    Inside MUSHclient.h are the global preferences, eg.

    
      unsigned int m_bAllTypingToCommandWindow;
      unsigned int m_bAlwaysOnTop;
      unsigned int m_bAppendToLogFiles;
      unsigned int m_bAutoConnectWorlds;
    


    The code for loading/saving global preferences is in globalregistryoptions.cpp.

  • World document (CMUSHclientDoc):


    • doc.cpp
    • doc.h


    This is the basic "connected world" processing. There are a lot of functions in doc.cpp, including connecting to a world, disconnecting, handling a packet from the MUD, and so on. In particular, processing of input is done in a "state machine" to handle telnet sequences (and MXP sequences) that might span multiple packets. Some of the telnet state machine is in telnet_phases.cpp.


    The constructor code for doc.cpp has been moved to doc_construct.cpp, because there is so much of it.


  • Other types (triggers / aliases / timers / plugins):


    • OtherTypes.h


    This file implements the declarations for commonly-used things like:


    • Lines (in the output window)
    • Style runs
    • Actions (eg. MXP actions)
    • Aliases
    • Triggers
    • Timers
    • Variables
    • Plugins



  • Notepad document (CTextDocument):


    • TextDocument.cpp
    • TextDocument.h


    This is the code for the internal notepad.

  • Activity window (CActivityDoc):


    • ActivityDoc.cpp
    • ActivityDoc.h


    This is the small "list of active worlds" window.


  • General text and other utilities:


    • Utilities.cpp



  • Script methods:


    • methods.cpp
    • world_debug.cpp
    • lua_methods.cpp
    • functionlist.cpp


    This rather large file (methods.cpp) implements almost all of the script functions. If you wanted to add another script function this would be the file to put it into. The "Debug" script function is implemented in its own file (world_debug.cpp).

    The file lua_methods.cpp implements "glue" routines, that make a Lua interface to the script functions in methods.cpp.

    For example, the Accelerator function:

    
    //----------------------------------------
    //  world.Accelerator
    //----------------------------------------
    static int L_Accelerator (lua_State *L)
      {
      CMUSHclientDoc *pDoc = doc (L);
      lua_pushnumber (L, pDoc->Accelerator (
            my_checkstring (L, 1),       // Key
            my_checkstring (L, 2)        // Send
            ));
      return 1;  // number of result fields
      } // end of L_Accelerator
    


    This effectively calls the "real" Accelerator function for the correct world, passing two arguments (key, and what to send), and returns the number, being the result code from Accelerator.

    The function "doc" attempts to find the correct document pointer - with Lua you can get a document other than the current one like this:

    
      otherworld = GetWorld (name)
    
      if otherworld == nil then
        Note ("World " .. name .. " is not open")
        return
      end
    
      Send (otherworld, message)
    
      -- alternative syntax:   otherworld:Send (message)
    


    Because of this, we have to check the first argument to the function call to see if it is a world userdata, and if so use that, otherwise default to the current world.

    There is a subtle problem with reporting the argument number if an argument is incorrect, because of the optional first argument, hence my_checkstring is a stub routine that works similarly to luaL_checkstring, but handles the problem with argument numbering.

    The file functionlist.cpp lists all known function names, for use with the help functions that do function-name completion, and so on.

  • End of line processing:


    • ProcessPreviousLine.cpp


    An extensive amount of processing occurs at end-of-line, thus this is placed in its own file. Stuff like trigger processing, line logging, line recolouring is done here.


  • Command evaluation:


    • evaluate.cpp


    This is where command evaluation is done (eg. alias processing).

  • Output window:


    • mushview.cpp
    • mushview.h


    This handles drawing of the output window, handling mouse clicks, and menu commands relevant to the output window.

  • Command window:


    • sendvw.cpp
    • sendvw.h


    This handles the command window - things like up-arrow, down-arrow, and related menu commands if the focus is in the command window. It also catches the <enter> key being pressed and works out whether to do a script, auto-say, etc.


  • Configuration:


    • CommandOptionsDlg.cpp
    • configuration.cpp
    • genpropertypage.cpp
    • GlobalPrefs.cpp
    • GlobalPrefsSheet.cpp
    • PrefsPropertyPages.cpp
    • PrefsPropertySheet.cpp
    • scriptingoptions.cpp
    • TreePropertySheet.cpp


    These handle the global and world-based configuration dialogs and other processing.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #1 on Mon 02 Apr 2007 05:26 AM (UTC)

Amended on Mon 02 Apr 2007 07:40 AM (UTC) by Nick Gammon

Message
Processing of data from the MUD takes quite a complex route. It goes like this:


  • CWorldSocket::OnReceive - notification data is waiting

  • CMUSHclientDoc::ProcessPendingRead - calls ReceiveMsg to obtain the data.

  • CMUSHclientDoc::ReceiveMsg - calls m_pSocket->Receive to obtain the packet. Decompresses packet data if MCCP is active. Then calls DisplayMsg to put in the output window.

  • CMUSHclientDoc::DisplayMsg - processes each character in the packet, passing it to the state machine via a big switch statement. When a newline is received, enters "end of line processing", by calling ProcessPreviousLine.

  • CMUSHclientDoc::ProcessPreviousLine - builds up the logical line from possibly multiple lines in the output window. That is, a lengthy line may cause soft line breaks, but not instigate trigger processing until the actual newline is received.

  • The line is now processed as described in http://www.gammon.com.au/forum/?id=6554




- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #2 on Mon 02 Apr 2007 07:46 AM (UTC)

Amended on Mon 02 Apr 2007 07:47 AM (UTC) by Nick Gammon

Message
Other general naming conventions are:


  • Member variables (of a class) are generally prefixed by m_, so for example m_mush_name is the variable holding the world (MUSH originally) name.

    Thus it follows that, in general, variables not starting with m_ are either local to a function, or global variables.

  • CString variables are often prefixed by "str", thus strResult is a CString containing "Result".

    This is not hard-and-fast, for example m_mush_name was devised before I had fully adopted the "str" convention.

  • Other string variables may start with "s", so for example sFoo, would be a string named "Foo". I tended to use this convention with "string" variables, as opposed to "CString" variables.

  • Integers generally start with "i", for example, iCount.

  • Constants are generally all caps, for example MAX_LINES.

  • Enumerated constants are often prefixed with "e", for example eConnecting, eConnected.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #3 on Mon 02 Apr 2007 07:49 AM (UTC)
Message
A great book on STL

I cannot recommend highly enough the book on STL by Nicolai Josuttis. This is a great read, and explains the STL library in great detail.


The C++ Standard Library - A Tutorial and Reference
By Nicolai M. Josuttis
Published by Addison-Wesley
ISBN: 0-201-37926-0
Cover price: $US 59.99

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #4 on Wed 13 Jun 2007 05:21 PM (UTC)

Amended on Wed 13 Jun 2007 05:54 PM (UTC) by Nick Gammon

Message
Localization

Version 4.09 onwards introduce extra code to localize the appearance of MUSHclient to the locale-specific language.

This is discussed at some length here:

http://www.gammon.com.au/forum/?id=7953


Internally this is implemented in a number of places:


  • A replacement function for AfxMessageBox called UMessageBox (Unicode Message Box) was written. This functions similarly to AfxMessageBox however it assumes (and checks) that its message is UTF-8, and then converts it to wide-characters (Unicode) before calling MessageBoxW to display the Unicode message.

    If the message is not UTF-8 it falls back to calling AfxMessageBox.

  • An extra Lua script space is created at program startup, and a localization script loaded from <MUSHclient executable folder>\locale\xx.lua, where xx is the locale code (eg. EN, FR, DE).

    The locale code is stored in the Registry at:


    HKEY_CURRENT_USER\Software\Gammon Software Solutions\MUSHclient\Global prefs\Locale


  • If this file is not found, or there is an error loading the localization script (eg. compile error), then localization is disabled for this session. That is, all message will appear in the original English.

  • New utility functions have been written to take an existing English string, translate them according to what is in the localization script, and return the translated version.

    These are:


    • Translate --> translate static "messages"
    • TranslateTime --> translate static "times"
    • TranslateHeading --> translate static "headings"
    • TFormat --> translate formatted strings


    Also a function TMessageBox was written which simply calls UMessage box after calling Translate. In other words, it lets you do a translated message box in a single call.

    As described in the forum post mentioned above, the static messages (like "This field may not be blank") are simply looked up directly, and the replacement used, if found. Effectively, a simple Lua table lookup is done with the original (English) message as the key. If the item is found in the table, it is a string, and the string is not empty, then the item's value is used as the replacement.

    The TFormat function expects a string with format replacements (eg. "Size of %s must be %i to %i").

    It deduces the number of arguments in the string by searching for %x, and sets up a Lua function call by pushing the appropriate arguments onto the Lua stack. This function is then called to do whatever is necessary to produce the translated, formatted string. If the function fails, or does not exist, then the original C "Format" function is called to format the original English message.

    An example of such a function is:

    
    -- GoToLineDlg.cpp:45
      ["Line number must be in range 1 to %i"] =
        function (a)
          return string.format (
            "La línea número debe estar en la gama 1 a %i.", a)
        end,  -- function
    


  • The source was scanned for strings submitted to AfxMessageBox, world.Note, world.ColourNote, SetStatus, and other places as far as possible, and the appropriate translation function inserted.

    For example:

    
    Before: ::AfxMessageBox("Line breaks not permitted here.");
    After:  ::TMessageBox("Line breaks not permitted here.");
    
    Before:  dlg.m_strTitle = "Edit trigger 'match' text";
    After:   dlg.m_strTitle = Translate ("Edit trigger 'match' text");
    
    Before:
    
          m_pDoc->ChatNote (eChatSession,
                      CFormat ("Chat session accepted, remote user: \"%s\"",
                        (LPCTSTR) m_strRemoteUserName));
    
    
    After:
    
          m_pDoc->ChatNote (eChatSession,
                      TFormat ("Chat session accepted, remote user: \"%s\"",
                        (LPCTSTR) m_strRemoteUserName));
    
    


  • The macro Translate_NoOp is a special case used in places (like tables of error messages) where translation is not to take place immediately, but we want the strings to appear in the localization file.

    For example:

    
    int_flags_pair error_descriptions[] =
    {
    
      {  0,      Translate_NoOp ("No error") },
      {  30001,  Translate_NoOp ("The world is already open") },
      {  30002,  Translate_NoOp ("The world is closed, this action cannot be performed") },
    
    ...
    


    The macro Translate signals the xgettext utility (mentioned below) that the strings "No error" (etc.) are to be entered into the translation file. However they are no-ops in the sense that the translation is done later, as they are inside a static table.

  • In version 4.10 onwards the resources part of the client were split into a separate DLL (and therefore a separate project). Early in the load process MUSHclient deduces the correct DLL name (eg. EN.dll, FR.dll, DE.dll etc.), and calls LoadLibrary to load that DLL, and set it as the source of program resources.

    This makes it much easier to edit the resources on their own and localize menus, dialogs, etc.

  • A localization batch file (localize.bat) was written, which calls xgettext (part of the gettext suite) to scan the MUSHclient source files, looking for lines which use Translate, TranslateTime, TranslateHeading, TFormat and Translate_NoOp. These are automatically written out to a series of text files.

  • A Lua program (localize.lua) was written, which takes the output from the previous step and converts those files into a template localization file (Localize_template.lua).

    It deduces the correct number of arguments for the formatted calls by looking for %x sequences, and generates the template functions, ready for the localizer to fill in the appropriate information.

    The intention here is that localize.bat will be called when changes are made to the MUSHclient source, to produce a new Localize_template.lua file.

    Localizers (ie. people who do the actual translations) will make a copy of that file to the appropriate name (eg. fr.lua), edit it, and replace the "empty" translated strings by the correct translations.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


14,435 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.