May 08, 2024, 05:32:38 AM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


Ani-Mates: An Ebasic MMO Game

Started by Hootie, November 29, 2008, 04:48:07 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Hootie

Ani-Mates Project Summary

Ani-Mates is a massive multiplayer on-line game being developed primarily in EBasic.  Code for the game client, world server, realm server and tools will be released to the Ionic Wind community under a commercial-friendly ZLib style license and may be used in part or in whole by anyone for any project.  The development will also serve as a basis for tutorials on using EBasic for large-scale application development with an emphasis on real-time 3D games.

Estimated Project Duration: 1+ years

Features: DirectX 9 shader-based engine, tcp/ip game server targeting 1000 simultaneous clients per server, realm/login server supporting any number of users and game servers, content tools such as a world editor


I am releasing my work as I go to help promote Ionic Wind tools as a viable alternative for creating serious applications.  Since much of the code is usable for wide variety of applications, I hope people can use it to learn and create many different things.

Please try to keep this thread clear for the posting of code and tutorials.  If you have questions or comments concerning the code or tutorials please message me or create a different thread so this one doesn't get real cluttered.  Thanks!

Hootie

Tutorial 1: Project Setup

Large programming projects typically consist of hundreds of code files which are used to produce one or more executable programs.  Due to the shear number of files involved it is best to organize your code into some sort of folder hierarchy from the start.  We will also need to use the Project feature of EBasic because our programs will consist of many source files each.

For Ani-Mates I will create a new, top-level folder on my harddrive like so:




Since Ani-Mates consists of four major pieces (Client, Realm Server, Game Server and Tools) I will create a separate sub-folder for each of these pieces and also a sub-folder called bin.  The bin sub-folder is where I'll store all my compiled code so it is away from the source code and intermediate files Ebasic creates during compiles.  This makes the compiled programs easy to find without a lot of surrounding clutter.




I'll be starting my work on the client program first.  This is the program that a user will have on their computer to run the game.  The client program will consist of many files so I will subdivide the client folder into more sub-folders to provide organization to all the source code of the client program.



Note: There will probably be more folders added under client as the project progresses but these will do for now.


Now, we are ready to create an EBasic project for the client program so start up the EBasic IDE and do the following:

1) Select File->New->Project from the menu
2) This will bring up the New Project settings dialog which we will fill out like this:



Note: Our project path is our \Animates\client directory.  The output file directory (where the compiled .exe is stored) is the \Animates\bin directory.

3) Click ok when finished.

The IDE will now create an EBasic project file (.ebp) named Animates Client.ebp in the \Animates\client directory.

This completes the Project Setup tutorial.  Now we are ready to start coding. 

Hootie

Here are a few additional notes before we start the coding:

1) You will need to install Sapero's Header Files which can be found here:  http://www.ionicwind.com/forums/index.php/topic,633.0.html

2) All code I post is subject to this license:

------------------------------------------------------------------------------------------------------------
Ani-Mates Source Code License

Copyright (c) 2008, 2009  Cheyenne Cloud, LLC.  All Rights Reserved.

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
    distribution.
------------------------------------------------------------------------------------------------------------

3) The code style I use is by and large procedural.  This is a my personal preference and nothing else should be read into it.  The code base is clean and if anyone desires to rewrite the code in OOP style, feel free.

4) I am not the world's best programmer.  Please feel free to share any and all comments, improvements or criticisms to the code I post.  I won't be offended in the slightest.





Hootie

Tutorial 2: High-Level Control Logic Stubs

All programs of any substance follow a simple three step process of execution which is:

Initialize
Process
Shutdown


These steps can be defined as follows:

Initialize - Obtain all computer resources needed by the program, initialize variables and subsystems, etc.. Basically anything that needs to be done to prepare the program to run.
Process - The execution of the main purpose of the program, whatever that may be.
Shutdown - Free the resources used by the program, close files, etc..  Basically clean up after the program before returning to the operating system.


The Ani-Mates client is no different than any other program so we will start our coding by defining this three step process as stub code.  Stub code can be defined as a basic infrastructure that may not do anything initially.  By setting up stub code you create high-level control logic and placeholders that can be revisited at a later time to flesh out more.

Since this code represents the highest level of logic in our program I'll create a source code file named amMain.eba which will be saved to the \client\main folder.  I will also add amMain.eba to my project in the EBasic IDE with the menu command Project->Insert File into Project.

Here is the code:


'****************************************************************************************
'*  amMain.eba                                                                          *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Program entry point for Ani-Mates client.                              *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************
$main


'=================================================================================
' Includes
'=================================================================================



'=================================================================================
' Run Program
'=================================================================================
amMain()
END



'=================================================================================
' amMain()
'
' This function is the highest level control logic in the program
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amMain()

amInitialize()
amProcess()
amShutdown()

ENDSUB



'=================================================================================
' amInitialize()
'
' This function calls all of the subsystem initializations
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amInitialize()



ENDSUB



'=================================================================================
' amProcess()
'
' This function controls the main processing loop
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amProcess()



ENDSUB



'=================================================================================
' amShutdown()
'
' This function calls all of the subsystem shutdown functions
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amShutdown()



ENDSUB




Code Notes:

- The $main command tells the EBasic compiler that this file is the entry point (where execution starts) for the program.
- I preface many functions with the letters "am" to help avoid naming conflicts with EBasic and third party library functions
- This code does nothing yet but you can compile it cleanly into an .exe.  We should always be able to cleanly compile an .exe after each tutorial.


Note to experienced users: 

I'm taking it slow to allow for lesser experienced users to follow along.  The pace will pick up soon.   :)

Hootie

November 30, 2008, 06:02:29 PM #4 Last Edit: November 30, 2008, 06:18:15 PM by Hootie
Tutorial 3: Graceful Shutdowns

In the last tutorial we went over the the three step process of program execution.  In a large-scale program like the Ani-Mates client, initialization typically involves initializing a series of sub-systems in a certain order.  The shutdown, in turn, shuts down these sub-systems in the reverse order that they were initialized.  In a perfect world handling something like this would be simple as follows:



SUB amInitialize()

    InitSystemA()
    InitSystemB()
    InitSystemC()
    InitSystemD()

ENDSUB

SUB amShutdown()

    ShutdownSystemD()
    ShutdownSystemC()
    ShutdownSystemB()
    ShutdownSystemA()

ENDSUB



But what happens if something fails and we need to shutdown from whatever point in the program we are at?  What if the program failed somewhere in one of the sub-system initializations and not all sub-systems are running yet?  If we try to shutdown a sub-system that hasn't been initialized, the program will probably crash.  What can we do?

The first thought that may come to mind is to set up a bunch of switch variables, one for each sub-system, and turn them each on as each sub-system successfully initializes.  Our shutdown process now becomes full of IF statements testing to see if a sub-system was successful before calling it's shutdown function.  This looks messy especially when you're talking about 20 or more sub-systems.

Also, keep in mind that if we switch the order that sub-systems are initialized or add new sub-systems, we also have to keep the shutdown function in sync with any changes.


Well, there is a better way and this is where our next piece of coding comes in.  Since shutdown functions do not require parameters and don't usually return any values, they share the same logical function definition.  Thus, we can automate the shutdown process by creating a LIFO (Last In, First Out) stack of function pointers to all the shutdown functions in the sub-systems.  We will also add a function for each sub-system to register it's shutdown function with the stack when it is finished initializing.

I will now create two more code files in the \main folder, amShutdownStack.eba and amShutdownStack.inc and add amShutdownStack.eba to my project.

Here is the code:


'****************************************************************************************
'*  amShutdownStack.eba                                                                 *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: LIFO stack of shutdown function pointers                               *
'*                                                                                      *
'*  This creates a LIFO stack of function pointers to the shutdown processes of the     *
'*  system.  Part of the initialization of each major subsystem will be to register     *
'*  a pointer to it's shutdown function here.  When the program needs to shutdown,      *
'*  either normally or due to a serious error, it calls this code to attempt a graceful *
'*  shutdown of all currently running subsystems.  The LIFO nature of the stack ensures *
'*  that shutdown functions are called in the reverse of the order they were            *
'*  initialized.                                                                        *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Includes
'=================================================================================
$INCLUDE "malloc.inc"


'=================================================================================
' Function Templates
'=================================================================================
DECLARE ShutdownFunction()


'=================================================================================
' User Defined Types
'=================================================================================
TYPE ShutDownEntry
DEF ShutdownCall:UINT
DEF PreviousEntry:POINTER
ENDTYPE


'=================================================================================
' Source Global Variables
'=================================================================================
INT LifoCount = 0
POINTER LastEntry = 0



'=================================================================================
' amShutdownRegister()
'
' This function registers a shutdown function into the LIFO stack.
'
' Parameters:  fn - Pointer to a shutdown function
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amShutdownRegister(UINT fn)

POINTER newentry

newentry = malloc(LEN(ShutDownEntry))

#<ShutDownEntry>newentry.ShutdownCall = fn
#<ShutDownEntry>newentry.PreviousEntry = LastEntry

LifoCount = LifoCount + 1
LastEntry = newentry

ENDSUB



'=================================================================================
' amShutdownAll()
'
' This function iterates through the LIFO stack and calls each shutdown function
' in the stack.  The stack memory is released as each entry is used.
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amShutdownAll()

POINTER functionentry
UINT callfunction

WHILE LifoCount > 0
functionentry = LastEntry
callfunction = #<ShutDownEntry>functionentry.ShutdownCall

!<ShutdownFunction>callfunction()

LastEntry = #<ShutDownEntry>functionentry.PreviousEntry
LifoCount = LifoCount - 1

free(functionentry)
ENDWHILE

ENDSUB




'****************************************************************************************
'*  amShutdownStack.inc                                                                 *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: LIFO stack of shutdown function pointers                               *
'*                                                                                      *
'*  This creates a LIFO stack of function pointers to the shutdown processes of the     *
'*  system.  Part of the initialization of each major subsystem will be to register     *
'*  a pointer to it's shutdown function here.  When the program needs to shutdown,      *
'*  either normally or due to a serious error, it calls this code to attempt a graceful *
'*  shutdown of all currently running subsystems.  The LIFO nature of the stack ensures *
'*  that shutdown functions are called in the reverse of the order they were            *
'*  initialized.                                                                        *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'================================================================================
' Functions
'=================================================================================
DECLARE EXTERN amShutdownRegister(UINT fn)     ' Register a shutdown function
DECLARE EXTERN amShutdownAll()                 ' Call all shutdown functions in stack



I will also add code to use the amShutdownStack.eba functions in amMain.eba that we created in the previous tutorial. 

amMain.eba now looks like this:


'****************************************************************************************
'*  amMain.eba                                                                          *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Program entry point for Ani-Mates client.                              *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************
$main


'=================================================================================
' Includes
'=================================================================================
$INCLUDE ".\main\amShutdownStack.inc"



'=================================================================================
' Run Program
'=================================================================================
amMain()
END



'=================================================================================
' amMain()
'
' This function is the highest level control logic in the program
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amMain()

amInitialize()
amProcess()
amShutdown()

ENDSUB



'=================================================================================
' amInitialize()
'
' This function calls all of the subsystem initializations
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amInitialize()



ENDSUB



'=================================================================================
' amProcess()
'
' This function controls the main processing loop
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amProcess()



ENDSUB



'=================================================================================
' amShutdown()
'
' This function calls all of the subsystem shutdown functions
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amShutdown()

amShutdownAll()

ENDSUB



jerryclement

Hootie,
I really appreciate your work on this tutorial. Thank you for your efforts.
I have finished the 3rd tutorial and have an error compiling it as below:

-------------------------------------------------------------------------------------
Compiling...
amMain
amShutdownStack

Linking...
Emergence Linker v1.11 Copyright ÂÃ,© 2006 Ionic Wind Software
Error: Bad library C:\Program Files\EBDev\libs\crtdll.lib: Symbol __imp_free in dictionary, but not in specified module

Error(s) in linking C:\Animates\bin\Animates_Client.exe
--------------------------------------------------------------------------------------

I have crtdll in EBDev\libs\ folder, date is 11/12/2003.
Do you know what this error indicates??
Any help is appreciated.

Sincerely,
JerryC


Jerry - Newbie from TN

Hootie

Tutorial 4: Handling Errors

Building a large application is an exercise in creating layers of code.  Each layer provides support for the next layer above it. 

As we start building the bottom layers of our application, we will first create functionality that almost everything else will depend on.  Probably the single most referred to function is an error handler.  Everything else uses it because errors can happen pretty much anywhere in a program.

There are many ways to handle errors.  I favor having a log file.  A log file is not only handy for spitting out error messages but it can also store informational messages that are helpful in determining what is happening internally in your program.

I will now create code for a log file named amLog.eba and amLog.inc and save them in the \log directory.  I will also add amLog.eba to my project.

Here is the code:


'****************************************************************************************
'*  amLog.eba                                                                           *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Logging messages to a file                                             *
'*                                                                                      *
'*  This is a message and error logger that writes messages out to a log file.  The     *
'*  messages have different levels of severity:                                         *
'*                                                                                      *
'*                     Message = informational message                                  *
'*                     Warning = Possible error but won't end program                   *
'*                     Error = Major error, will stop execution of program              *
'*                                                                                      *
'*  Message strings can be formatted with the Ebasic USING command.  The messages are   *
'*  not buffered and are immediately written to the log file.  This is to preserve the  *
'*  messages in case of program crash.                                                  *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************

'=================================================================================
' Includes
'=================================================================================
$INCLUDE "windowssdk.inc"
$INCLUDE ".\main\amShutdownStack.inc"



'=================================================================================
' Enums
'=================================================================================
ENUM ErrorLevel
    AMLOG_MESSAGE
    AMLOG_WARNING
    AMLOG_ERROR
ENDENUM



'=================================================================================
' Source Global Variables
'=================================================================================
FILE fp = 0
STRING DefaultName = "log.log"



'=================================================================================
' amLogInit()
'
' This function creates the log file and opens it.
'
' Parameters:  logname - Desired log file name string
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amLogInit(STRING logname)

IF (OPENFILE(fp, logname, "W")) <> 0
END
ENDIF

LogWrite(AMLOG_MESSAGE, "***** Log System Initializing *****")
amShutdownRegister(&amLogShutdown)

ENDSUB



'=================================================================================
' amLogShutdown()
'
' This function closes the log file.
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amLogShutdown()

    IF fp <> 0
LogWrite(AMLOG_MESSAGE, "***** Log System shutting down *****")
CLOSEFILE fp
ENDIF

    fp = 0

ENDSUB



'=================================================================================
' amLogError()
'
' Log Error level messages. End program.
'
' Parameters:  msg - Error message string
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amLogError(STRING msg)

    ' Print message to log file
    LogWrite(AMLOG_ERROR, msg)

' Shutdown
    amShutdownAll()

    ' Abort program
    END

ENDSUB



'=================================================================================
' amLogWarning()
'
' Log Warning level messages.
'
' Parameters:  msg - Warning message string
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amLogWarning(STRING msg)

    ' Print message to log file
    LogWrite(AMLOG_WARNING, msg)

ENDSUB



'=================================================================================
' amLogMessage()
'
' Log Informational level messages.
'
' Parameters:  msg - Message string
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amLogMessage(STRING msg)

    ' Print message to log file
    LogWrite(AMLOG_MESSAGE, msg)

ENDSUB



'=================================================================================
' LogWrite()
'
' Write messages to file.
'
' Parameters:  level - Enum level of message
'              msg   - Message string
'
' Returns: Nothing
'     
'=================================================================================
SUB LogWrite(ErrorLevel level, STRING msg)

STRING msgtype
STRING finalmsg


    ' If file doesn't exist, create it
IF fp = 0
amLogInit(DefaultName)
ENDIF

    ' Determine message level label for message
    if level = AMLOG_MESSAGE
        msgtype = "INFO: "
ENDIF

    if level = AMLOG_WARNING
        msgtype = "WARNING: "
ENDIF

    if level = AMLOG_ERROR
        msgtype = "ERROR: "
ENDIF

    finalmsg = msgtype + msg

    ' Write message to file and force buffer flush
    WRITE fp, finalmsg
    FlushFileBuffers(fp)

ENDSUB




'****************************************************************************************
'*  amLog.inc                                                                           *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Logging messages to a file                                             *
'*                                                                                      *
'*  This is a message and error logger that writes messages out to a log file.  The     *
'*  messages have different levels of severity:                                         *
'*                                                                                      *
'*                     Message = informational message                                  *
'*                     Warning = Possible error but won't end program                   *
'*                     Error = Major error, will stop execution of program              *
'*                                                                                      *
'*  Message strings can be formatted with the Ebasic USING command.  The messages are   *
'*  not buffered and are immediately written to the log file.  This is to preserve the  *
'*  messages in case of program crash.                                                  *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************



'================================================================================
' Functions
'=================================================================================
DECLARE EXTERN amLogInit(STRING logname)     ' Opens log file
DECLARE EXTERN amLogShutdown()               ' Closes log file
DECLARE EXTERN amLogError(STRING msg)        ' Logs an error, ends program
DECLARE EXTERN amLogWarning(STRING msg)      ' Logs an warning
DECLARE EXTERN amLogMessage(STRING msg)      ' Logs a message



I will also add new code into amMain.eba to activate the log sub-system and send it a little test.

amMain.eba now looks like this:


'****************************************************************************************
'*  amMain.eba                                                                          *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Program entry point for Ani-Mates client.                              *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************
$main


'=================================================================================
' Includes
'=================================================================================
$INCLUDE ".\main\amShutdownStack.inc"
$INCLUDE ".\log\amLog.inc"



'=================================================================================
' Run Program
'=================================================================================
amMain()
END



'=================================================================================
' amMain()
'
' This function is the highest level control logic in the program
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amMain()

amInitialize()
amProcess()
amShutdown()

ENDSUB



'=================================================================================
' amInitialize()
'
' This function calls all of the subsystem initializations
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amInitialize()

    amLogInit("animates.log")


ENDSUB



'=================================================================================
' amProcess()
'
' This function controls the main processing loop
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amProcess()

' Test the log file
    amLogMessage("This is a test")
    amLogWarning("This is another test")


ENDSUB



'=================================================================================
' amShutdown()
'
' This function calls all of the subsystem shutdown functions
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amShutdown()

amShutdownAll()

ENDSUB


Once you compile the client program with these additions it will actually do a little something.  Go to the \bin directory and double click on Animates_Client.exe.  The program will run and create a file called animates.log which should look like this if you open it in notepad:


INFO: ***** Log System Initializing *****
INFO: This is a test
WARNING: This is another test
INFO: ***** Log System shutting down *****


Code Notes:

- Notice that there are three levels of message functions: 
    amLogMessage - Use for informational messages
    amLogWarning - Use for errors that are not critical enough to stop the application
    amLogError - Use for critical errors.  This will end the program.

- Notice that the amLogError function uses the code we wrote in the last tutorial to attempt a graceful shutdown
- Notice that the amLogInit function registers it's shutdown function with the shutdown stack.  This is our first sub-system.   :)

Hootie

December 03, 2008, 01:25:46 PM #7 Last Edit: December 04, 2008, 08:10:06 AM by Hootie
Tutorial 5: Time Budget and Profiling

When developing a game, you have a definite time budget.  Your game needs to run at least 30 FPS (Frames Per Second) on the minimum system configuration you are targeting.  Any slower than that and your game will appear jittery to the user.  This defines a time budget of 1/30 of a second for your program to completely produce one frame of graphics, sound, animation, AI, etc..  1/30 of a second is .0333 seconds.  This is not a long time so your code has to be as efficient as possible.

So, how do we make sure we are writing efficient code?  Let's say we complete our game and it runs way too slow to be playable.  How do we tell where we need to work on speeding it up?

This brings us to our next piece of code, a profiler.  A profiler, or specifically a code profiler, measures the speed that a selection of code runs at.  We will be using the profiler during our development to help us write fast code and make sure we are staying within our time budget.

Now I will create two new code files, amProfile.eba and amProfile.inc and save them under the \time folder.  I will also add amProfile.eba to my project.

Here is the code:


'****************************************************************************************
'*  amProfile.eba                                                                       *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Code Profiler                                                          *
'*                                                                                      *
'*  This is a code profiler that measures the time taken to execute a section of        *
'*  program code.                                                                       *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Includes
'=================================================================================
$INCLUDE "windowssdk.inc"
$INCLUDE "malloc.inc"
$INCLUDE "memory.inc"

$INCLUDE ".\log\amLog.inc"
$INCLUDE ".\main\amShutdownStack.inc"


'=================================================================================
' User Defined Types
'=================================================================================
TYPE ProfileSection             ' Code section being profiled
DEF Iterations:INT          ' Number of times code section was executed
DEF TotalTime:DOUBLE        ' Total time of all executions in seconds
DEF Description:POINTER     ' Description of code section
    DEF StartTime:INT64         ' Start time of most recent execution
ENDTYPE


'=================================================================================
' Source Global Variables
'=================================================================================
POINTER ProfileArray = NULL
DOUBLE SecondsPerCount = 0.0
INT ProfileSectionSize = 0
POINTER EmptySlot = NULL
INT TotalProfiles = 0



'=================================================================================
' amProfileInit()
'
' This function initializes the code profiler.
'
' Parameters:  profilecount - The number of code sections being profiled
'
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amProfileInit(INT profilecount)

INT64 countspersecond = 0


    amLogMessage("***** Profile System Initializing *****")

ProfileSectionSize = LEN(ProfileSection)

    ' Create an array to keep track of the sections being profiled
    ProfileArray = calloc(ProfileSectionSize * (profilecount + 1), 1)

    IF ProfileArray = NULL
amLogError("amProfileInit failed on ProfileArray create!")
ENDIF

    EmptySlot = calloc(ProfileSectionSize, 1)

    IF EmptySlot = NULL
amLogError("amProfileInit failed on EmptySlot create!")
ENDIF

TotalProfiles = profilecount

QueryPerformanceFrequency(&countspersecond)
SecondsPerCount = 1.0 / countspersecond

amShutdownRegister(&amProfileShutdown)

ENDSUB



'=================================================================================
' amProfileShutdown()
'
' This function shuts down the code profiler.
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amProfileShutdown()

    amLogMessage("***** Profile System shutting down *****")

    ' Write profile information to log
    WriteProfileLog()

    ' Delete the profile array
    free(ProfileArray)
    free(EmptySlot)

ENDSUB



'=================================================================================
' amProfileStart()
'
' This function records the start of a code section to be profiled.
'
' Parameters:  profileid - The unique numeric id of this profile section.
'              profiledesc - Pointer to description of the section being profiled.
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amProfileStart(INT profileid, POINTER profiledesc)

POINTER prof = NULL
INT64 stime = 0


IF profileid > TotalProfiles
        amLogError(USING("&###", "amProfileStart subscript out of range: ", profileid))
ENDIF

' Get the profile entry
prof = ProfileArray + (ProfileSectionSize * profileid)

' Check to see if slot is empty
    IF memcmp(prof, EmptySlot, ProfileSectionSize) = 0
#<ProfileSection>prof.Iterations = 0
#<ProfileSection>prof.TotalTime = 0.0
#<ProfileSection>prof.Description = profiledesc
ENDIF

QueryPerformanceCounter(&stime)
#<ProfileSection>prof.StartTime = stime

ENDSUB



'=================================================================================
' amProfileEnd()
'
' This function records the end of a code section to be profiled.
'
' Parameters:  profileid - The unique numeric id of this profile section.
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amProfileEnd(INT profileid)

POINTER prof = NULL
INT64 etime = 0
DOUBLE deltatime = 0.0


IF profileid > TotalProfiles
        amLogError(USING("&###", "amProfileStart subscript out of range: ", profileid))
ENDIF

' Get the profile entry
prof = ProfileArray + (ProfileSectionSize * profileid)

QueryPerformanceCounter(&etime)
deltatime = (etime - #<ProfileSection>prof.StartTime) * SecondsPerCount

#<ProfileSection>prof.Iterations = #<ProfileSection>prof.Iterations + 1
#<ProfileSection>prof.TotalTime = #<ProfileSection>prof.TotalTime + deltatime

ENDSUB



'=================================================================================
' WriteProfileLog()
'
' This function writes all the profiled information to a profile log file
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB WriteProfileLog()

POINTER prof = NULL
INT i = 0
FILE fp = 0
STRING str = ""
POINTER strptr = NULL


IF (OPENFILE(fp, "profile.log", "W")) <> 0
       amLogError("Error opening log file in WriteProfileLog.")
ENDIF

FOR i = 1 TO TotalProfiles
prof = ProfileArray + (ProfileSectionSize * i)

IF memcmp(prof, EmptySlot, ProfileSectionSize) <> 0
strptr = #<ProfileSection>prof.Description
str = #<STRING>strptr
WRITE fp, USING("&&##########.##########&##########.##########", str, " total time = ", #<ProfileSection>prof.TotalTime, "  Average time = ", (#<ProfileSection>prof.TotalTime / #<ProfileSection>prof.Iterations))
ENDIF
NEXT i

CLOSEFILE fp

ENDSUB



'****************************************************************************************
'*  amProfile.inc                                                                       *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Code Profiler                                                          *
'*                                                                                      *
'*  This is a code profiler that measures the time taken to execute a section of        *
'*  program code.                                                                       *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Functions
'=================================================================================
DECLARE EXTERN amProfileInit(INT profilecount)                                      ' Initialize code profiler
DECLARE EXTERN amProfileShutdown()                                                  ' Shutdown code profiler
DECLARE EXTERN amProfileStart(INT profileid, POINTER profiledesc)               ' Start of a code section to profile
DECLARE EXTERN amProfileEnd(INT profileid)                                          ' End of a code section to profile



We will be using our new profiler in the next tutorial.


****UPDATE: Removed a couple of unused variables from amProfileInit.

Hootie

Tutorial 6: Fast Memory Allocation

Memory allocators such as EBasic's NEW/DELETE and C's malloc/free are designed to be as efficient as possible for general memory allocations.  Games typically do a lot of small, dynamic allocations during runtime and as such need these allocations to run as fast as possible.  Speed is of the utmost importance, more than say a little memory wasting (at least on PCs, not so on consoles).

This brings us to our next sub-system, a high-speed memory allocator.

The memory allocator design makes use of a collection of memory pools which are pre-allocated blocks of memory cut into programmer specified chunk sizes.  When the memory allocator receives an allocation request it selects the pool with nearest size chunk to the allocation and sends the address of a free chunk from the pool to the requester.  Also of note is that each chunk has the first 8 bytes used for storing indexes of the pool and chunk it is.  This is to speed up the the freeing of the chunk once the program is done using it. 

Now I'll create two new code files, amMemory.eba and amMemory.inc and save them under the \memory folder.  I will also add amMemory to my project.

Here is the code:


'****************************************************************************************
'*  amMemory.eba                                                                        *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Memory manager for dynamically allocated memory                        *
'*                                                                                      *
'*  This is a memory manager for dynamic memory allocation requests.  The manager       *
'*  maintains a set of pre-allocated memory pools from which it doles out chunks to     *
'*  satisfy the dynamic memory requests of the program that uses it.                    *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Includes
'=================================================================================
$INCLUDE "malloc.inc"
$INCLUDE "memory.inc"

$INCLUDE ".\log\amLog.inc"
$INCLUDE ".\main\amShutdownStack.inc"


'=================================================================================
' User Defined Types
'=================================================================================
TYPE AllocBlock               ' Allocation within a memory pool
    DEF NextFree:INT          ' Subscript of next free space
    DEF PoolNumber:POINTER    ' Pointer to pool number
    DEF Subscript:POINTER     ' Pointer to subscript
    DEF Start:POINTER         ' Pointer to the start of this allocation
ENDTYPE

TYPE MemoryPool               ' Memory pool
    DEF Pool:POINTER          ' Pointer to the pool space
DEF AllocArray:POINTER    ' Pointer to allocation array
    DEF FreeListHead:INT      ' Subscript to first free space in list
    DEF FreeListTail:INT      ' Subscript to last free space in list
DEF ItemSize:INT          ' Size of items for this pool in bytes
DEF ItemTotal:INT         ' Total number of item slots in pool
    DEF PoolNumber:INT        ' Pool number
ENDTYPE


'=================================================================================
' Source Global Variables
'=================================================================================
POINTER PoolArray = NULL        ' Pointer to array of available pools
INT TotalPools = 0              ' Total number of pools
INT MemoryPoolSize = 0          ' Size of the MemoryPool structure
INT AllocBlockSize = 0          ' Size of the AllocBlock structure
INT PoolAddedCount = 0          ' Number of pools added
POINTER FastPoolFinder = NULL   ' Fast pool index finder array
INT MaxItemSize = 0             ' Maximum allocation size defined by pools


'=================================================================================
' amMemoryInit()
'
' This function initializes the memory manager.
'
' Parameters:  poolcount - The number of pools that will be used
'             
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amMemoryInit(INT poolcount)

    amLogMessage("***** Memory System Initializing *****")

MemoryPoolSize = LEN(MemoryPool)
AllocBlockSize = LEN(AllocBlock)

    ' Create an array to keep track of the pools
    PoolArray = malloc(MemoryPoolSize * (poolcount + 1))

    IF PoolArray = NULL
amLogError("amMemoryInit failed on PoolArray create!")
ENDIF

TotalPools = poolcount

amShutdownRegister(&amMemoryShutdown)

ENDSUB



'=================================================================================
' amMemoryShutdown()
'
' This function shuts down the memory manager and frees all pools.
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amMemoryShutdown()

    INT i = 0
    POINTER mempool = NULL


    amLogMessage("***** Memory System shutting down *****")

    ' Pass through array and delete all pools found
    FOR i = 1 TO TotalPools
        mempool = PoolArray + (MemoryPoolSize * i)

free(#<MemoryPool>mempool.Pool)
free(#<MemoryPool>mempool.AllocArray)
NEXT i

    ' Delete the pool array and fast finder array
    free(PoolArray)
free(FastPoolFinder)

ENDSUB



'=================================================================================
' amMemoryAddPool()
'
' This function adds a memory pool to the manager.
'
' Parameters:  blocksize - The size of a memory bloack this pool will allocate
'              blockcount - The number of blocks in the pool
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amMemoryAddPool(INT blocksize, INT blockcount)

    INT i = 0
MemoryPool mempool
AllocBlock allocblk
POINTER ptr = NULL


PoolAddedCount = PoolAddedCount + 1

IF PoolAddedCount > TotalPools
amLogError("amMemoryAddPool new pool exceeded total allowed pools!")
ENDIF

mempool.Pool = malloc(blocksize * blockcount)

IF mempool.Pool = NULL
amLogError("amMemoryAddPool failed on Pool create!")
ENDIF

mempool.AllocArray = malloc(AllocBlockSize * (blockcount + 1))

IF mempool.AllocArray = NULL
amLogError("amMemoryAddPool failed on AllocArray create!")
ENDIF

FOR i = 1 to blockcount
IF i = blockcount
allocblk.NextFree = 0
ELSE
allocblk.NextFree = i + 1
ENDIF

allocblk.PoolNumber = mempool.Pool + (blocksize * (i - 1))
allocblk.Subscript = mempool.Pool + (blocksize * (i - 1)) + 4
allocblk.Start = mempool.Pool + (blocksize * (i - 1)) + 8

memcpy(mempool.AllocArray + (AllocBlockSize * i), &allocblk, AllocBlockSize)

ptr = allocblk.PoolNumber
#<INT>ptr = PoolAddedCount
ptr = allocblk.Subscript
#<INT>ptr = i
NEXT i

mempool.FreeListHead = 1
mempool.FreeListTail = blockcount
mempool.ItemSize = blocksize
mempool.ItemTotal = blockcount
mempool.PoolNumber = PoolAddedCount

memcpy(PoolArray + (MemoryPoolSize * PoolAddedCount), &mempool, MemoryPoolSize)

IF PoolAddedCount = TotalPools
CreateFastFinder()
ENDIF

ENDSUB



'=================================================================================
' amMemoryAllocate()
'
' This function allocates memory from the memory manager.
'
' Parameters:  n - The size in bytes of memory requested 
'
'
' Returns: Pointer to the memory allocated to satisfy the request
'     
'=================================================================================
GLOBAL SUB amMemoryAllocate(INT n), POINTER

INT realsize = 0
POINTER nativealloc = NULL
POINTER subscriptptr = NULL
POINTER mempool = NULL
POINTER allocblk = NULL
POINTER nextallocblk = NULL
POINTER returnptr = NULL
POINTER fastindex = NULL



realsize = n + 8

IF realsize <= MaxItemSize
fastindex = FastPoolFinder + (4 * realsize)
mempool = PoolArray + (MemoryPoolSize * #<INT>fastindex)
ELSE
nativealloc = malloc(realsize)

IF nativealloc = NULL
amLogError("amMemoryAllocate native allocate failed for oversized request!")
ENDIF

#<INT>nativealloc = 0

subscriptptr = nativealloc + 4
#<INT>subscriptptr = 0

returnptr = nativealloc + 8
RETURN returnptr
ENDIF

' Pool is out of space.  Allocate natively.
IF #<MemoryPool>mempool.FreeListHead = 0
nativealloc = malloc(realsize)

IF nativealloc = NULL
amLogError("amMemoryAllocate native allocate failed for overflowed pool request!")
ENDIF

' Comment this line out for final release version
amLogMessage(USING("&#############","amMemoryAllocate pool out of space!  Pool size: ", #<MemoryPool>mempool.ItemSize))

#<INT>nativealloc = 0

subscriptptr = nativealloc + 4
#<INT>subscriptptr = 0

returnptr = nativealloc + 8
RETURN returnptr
ENDIF

' Get the first free space available
allocblk = #<MemoryPool>mempool.AllocArray + (AllocBlockSize * #<MemoryPool>mempool.FreeListHead)

#<MemoryPool>mempool.FreeListHead = #<AllocBlock>allocblk.NextFree

IF #<AllocBlock>allocblk.NextFree = 0
#<MemoryPool>mempool.FreeListTail = 0
ENDIF

    RETURN #<AllocBlock>allocblk.Start

ENDSUB



'=================================================================================
' amMemoryFree()
'
' This function frees memory previously allocated from the memory manager.
'
' Parameters:  ptr - Pointer to the allocated memory to free 
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amMemoryFree(POINTER ptr)

POINTER poolptr = NULL
POINTER allocptr = NULL
POINTER allocblk = NULL
POINTER prevallocblk = NULL
POINTER mempool = NULL
INT poolsub = 0
INT allocsub = 0


poolptr = ptr - 8
allocptr = ptr - 4
poolsub = #<INT>poolptr
allocsub = #<INT>allocptr

IF poolsub = 0 AND allocsub = 0
free(ptr)
RETURN
ENDIF

mempool = PoolArray + (MemoryPoolSize * poolsub)
allocblk = #<MemoryPool>mempool.AllocArray + (AllocBlockSize * allocsub)

IF #<MemoryPool>mempool.FreeListHead = 0
#<MemoryPool>mempool.FreeListHead = allocsub
#<MemoryPool>mempool.FreeListTail = allocsub
RETURN
ENDIF

prevallocblk = #<MemoryPool>mempool.AllocArray + (AllocBlockSize * #<MemoryPool>mempool.FreeListTail)

#<AllocBlock>prevallocblk.NextFree = allocsub
#<AllocBlock>allocblk.NextFree = 0
#<MemoryPool>mempool.FreeListTail = allocsub

ENDSUB


SUB CreateFastFinder()
POINTER mempool = NULL
POINTER nextmempool = NULL
INT i = 0
INT j = 0
POINTER fastindex = NULL



' Get largest pool item size
mempool = PoolArray + (MemoryPoolSize * TotalPools)
MaxItemSize = #<MemoryPool>mempool.ItemSize

FastPoolFinder = malloc((MaxItemSize + 1) * 4)

IF FastPoolFinder = NULL
amLogError("CreateFastFinder failed on FastPoolFinder create!")
ENDIF

mempool = PoolArray + (MemoryPoolSize * 1)

FOR i = 1 TO #<MemoryPool>mempool.ItemSize
fastindex = FastPoolFinder + (4 * i)
#<INT>fastindex = #<MemoryPool>mempool.PoolNumber
NEXT i

FOR i = 2 TO TotalPools
nextmempool = PoolArray + (MemoryPoolSize * i)

FOR j = (#<MemoryPool>mempool.ItemSize + 1) TO #<MemoryPool>nextmempool.ItemSize
fastindex = FastPoolFinder + (4 * j)
#<INT>fastindex = #<MemoryPool>nextmempool.PoolNumber
NEXT j

mempool = nextmempool
NEXT i

ENDSUB




'****************************************************************************************
'*  amMemory.inc                                                                        *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Memory manager for dynamically allocated memory                        *
'*                                                                                      *
'*  This is a memory manager for dynamic memory allocation requests.  The manager       *
'*  maintains a set of pre-allocated memory pools from which it doles out chunks to     *
'*  satisfy the dynamic memory requests of the program that uses it.                    *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Functions
'=================================================================================
DECLARE EXTERN amMemoryInit(INT poolcount)                            ' Initialize memory manager
DECLARE EXTERN amMemoryShutdown()                                     ' Shutdown memory manager
DECLARE EXTERN amMemoryAddPool(INT blocksize, INT blockcount)         ' Add a pool to the memory manager
DECLARE EXTERN amMemoryAllocate(INT n), POINTER                       ' Allocates n byte memory block
DECLARE EXTERN amMemoryFree(POINTER ptr)                              ' Frees memory block



Now we'll set up amMain.eba to use the memory allocator and perform a little speed test using the profiler we created in the last tutorial.

amMain.eba now looks like this:


'****************************************************************************************
'*  amMain.eba                                                                          *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: Program entry point for Ani-Mates client.                              *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************
$main


'=================================================================================
' Includes
'=================================================================================
$INCLUDE "malloc.inc"

$INCLUDE ".\main\amShutdownStack.inc"
$INCLUDE ".\log\amLog.inc"
$INCLUDE ".\time\amProfile.inc"
$INCLUDE ".\memory\amMemory.inc"


'=================================================================================
' Source Global Variables
'=================================================================================
STRING prof1 = "Malloc and Free"
STRING prof2 = "New and Delete "
STRING prof3 = "Memory Manager "



'=================================================================================
' Run Program
'=================================================================================
amMain()
END



'=================================================================================
' amMain()
'
' This function is the highest level control logic in the program
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amMain()

amInitialize()
amProcess()
amShutdown()

ENDSUB



'=================================================================================
' amInitialize()
'
' This function calls all of the subsystem initializations
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amInitialize()

    amLogInit("animates.log")

'Profiling 3 code sections
amProfileInit(3)

' Allocating 6 memory pools
amMemoryInit(6)

' Must add memory pool sizes from smallest to largest
amMemoryAddPool(16, 100)
amMemoryAddPool(32, 100)
amMemoryAddPool(64, 100)
amMemoryAddPool(128, 100)
amMemoryAddPool(256, 100)
amMemoryAddPool(512, 100)


ENDSUB



'=================================================================================
' amProcess()
'
' This function controls the main processing loop
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amProcess()

' Some variables to test profiler and memory allocator
POINTER a1 = NULL
POINTER b1 = NULL
POINTER c1 = NULL
POINTER d1 = NULL
POINTER e1 = NULL
POINTER f1 = NULL
POINTER g1 = NULL
POINTER h1 = NULL
POINTER i1 = NULL
POINTER j1 = NULL
POINTER k1 = NULL
POINTER l1 = NULL
POINTER a2 = NULL
POINTER b2 = NULL
POINTER c2 = NULL
POINTER d2 = NULL
POINTER e2 = NULL
POINTER f2 = NULL
POINTER g2 = NULL
POINTER h2 = NULL
POINTER i2 = NULL
POINTER j2 = NULL
POINTER k2 = NULL
POINTER l2 = NULL
POINTER a3 = NULL
POINTER b3 = NULL
POINTER c3 = NULL
POINTER d3 = NULL
POINTER e3 = NULL
POINTER f3 = NULL
POINTER g3 = NULL
POINTER h3 = NULL
POINTER i3 = NULL
POINTER j3 = NULL
POINTER k3 = NULL
POINTER l3 = NULL
INT i = 0


'Compare NEW/DELETE vs malloc/free vs Memory Manager
FOR i = 1 to 1000
amProfileStart(1, &prof2)

a2 = NEW(CHAR, 50)
b2 = NEW(CHAR, 100)
c2 = NEW(CHAR, 500)
d2 = NEW(CHAR, 250)
e2 = NEW(CHAR, 10)
f2 = NEW(CHAR, 66)
g2 = NEW(CHAR, 50)
h2 = NEW(CHAR, 100)
i2 = NEW(CHAR, 500)
j2 = NEW(CHAR, 250)
k2 = NEW(CHAR, 10)
l2 = NEW(CHAR, 66)
DELETE a2
DELETE b2
DELETE c2
DELETE d2
DELETE e2
DELETE f2
DELETE g2
DELETE h2
DELETE i2
DELETE j2
DELETE k2
DELETE l2

amProfileEnd(1)
NEXT i

FOR i = 1 to 1000
amProfileStart(2, &prof1)

a1 = malloc(50)
b1 = malloc(100)
c1 = malloc(500)
d1 = malloc(250)
e1 = malloc(10)
f1 = malloc(66)
g1 = malloc(50)
h1 = malloc(100)
i1 = malloc(500)
j1 = malloc(250)
k1 = malloc(10)
l1 = malloc(66)
free(a1)
free(b1)
free(c1)
free(d1)
free(e1)
free(f1)
free(g1)
free(h1)
free(i1)
free(j1)
free(k1)
free(l1)

amProfileEnd(2)
NEXT i

FOR i = 1 to 1000
amProfileStart(3, &prof3)

a3 = amMemoryAllocate(50)
b3 = amMemoryAllocate(100)
c3 = amMemoryAllocate(500)
d3 = amMemoryAllocate(250)
e3 = amMemoryAllocate(10)
f3 = amMemoryAllocate(66)
g3 = amMemoryAllocate(50)
h3 = amMemoryAllocate(100)
i3 = amMemoryAllocate(500)
j3 = amMemoryAllocate(250)
k3 = amMemoryAllocate(10)
l3 = amMemoryAllocate(66)
amMemoryFree(a3)
amMemoryFree(b3)
amMemoryFree(c3)
amMemoryFree(d3)
amMemoryFree(e3)
amMemoryFree(f3)
amMemoryFree(g3)
amMemoryFree(h3)
amMemoryFree(i3)
amMemoryFree(j3)
amMemoryFree(k3)
amMemoryFree(l3)

amProfileEnd(3)
NEXT i



ENDSUB



'=================================================================================
' amShutdown()
'
' This function calls all of the subsystem shutdown functions
'
' Parameters:  None
'
' Returns: Nothing
'     
'=================================================================================
SUB amShutdown()

amShutdownAll()

ENDSUB



Once you compile, go to \bin and double click on Animates_Client.exe.  The program will run and create yet another file called profile.log which is the output from the profiler.  The output looks like this on my machine (yours will vary in timings):


New and Delete  total time =          0.0046742154  Average time =          0.0000046742
Malloc and Free total time =          0.0029219636  Average time =          0.0000029220
Memory Manager  total time =          0.0019068623  Average time =          0.0000019069


As we can see, Memory Manager wins the speed test, followed by malloc/free with NEW/DELETE coming in last.

You may also want to take note of how your animates.log file now looks.  Notice how all our sub-systems shut down in reverse order and yet we have never had to maintain a series of shutdown calls.

Hootie

Tutorial 7: CPU Detection

Here's a little bit of code we will be using in future tutorials.  A very simple CPU detection routine.

I will now create amCPU.eba and amCPU.inc in the \machine directory.  I will also add amCPU.eba to my project.

Here is the code:


'****************************************************************************************
'*  amCPU.eba                                                                           *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: CPU detection                                                          *
'*                                                                                      *
'*  This module gathers information about the CPU the engine is running on.             *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Includes
'=================================================================================
$INCLUDE ".\log\amLog.inc"


'=================================================================================
' User Defined Types
'=================================================================================
TYPE CPUInfo                  ' Information about the CPU
    DEF Vendor:STRING         ' Vendor name
    DEF Name:STRING           ' CPU name
    DEF MMX:CHAR              ' Has MMX
    DEF SSE:CHAR              ' Has SSE
    DEF SSE2:CHAR             ' Has SSE2
ENDTYPE


'=================================================================================
' Global Variables
'=================================================================================
DEF GLOBAL gCPUInfo AS CPUInfo


'=================================================================================
' Source Global Variables
'=================================================================================
ISTRING Vendor[12]
ISTRING Name[48]
CHAR MMX
CHAR SSE
CHAR SSE2


'=================================================================================
' amCPUDetect()
'
' This function gets the CPU info
'
' Parameters:  None
'             
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amCPUDetect()

    amLogMessage("***** Detecting CPU *****")

    ' Get vendor
_asm
mov eax, 0
cpuid

mov esi, $Vendor
mov [esi], ebx
mov [esi + 4], edx
mov [esi + 8], ecx
_endasm

gCPUInfo.Vendor = Vendor

' Get CPU Name
_asm
mov eax, 0x80000002
cpuid

mov esi, $Name
mov [esi], eax
mov [esi + 4], ebx
mov [esi + 8], ecx
mov [esi + 12], edx

mov eax, 0x80000003
cpuid

mov [esi + 16], eax
mov [esi + 20], ebx
mov [esi + 24], ecx
mov [esi + 28], edx

mov eax, 0x80000004
cpuid

mov [esi + 32], eax
mov [esi + 36], ebx
mov [esi + 40], ecx
mov [esi + 44], edx
_endasm

gCPUInfo.Name = Name

' Test MMX
_asm
mov eax, 1
cpuid

test edx, 0x00800000
jz nommx
mov BYTE [$MMX], 1
jp exit1
nommx:
mov BYTE [$MMX], 0
exit1:
_endasm

gCPUInfo.MMX = MMX

' Test SSE
_asm
mov eax, 1
cpuid

test edx, 0x02000000
jz nosse
mov BYTE [$SSE], 1
jp exit2
nosse:
mov BYTE [$SSE], 0
exit2:
_endasm

gCPUInfo.SSE = SSE

' Test SSE2
_asm
mov eax, 1
cpuid

test edx, 0x04000000
jz nosse2
mov BYTE [$SSE2], 1
jp exit3
nosse2:
mov BYTE [$SSE2], 0
exit3:
_endasm

gCPUInfo.SSE2 = SSE2

ENDSUB




'****************************************************************************************
'*  amCPU.inc                                                                           *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: CPU detection                                                          *
'*                                                                                      *
'*  This module gathers information about the CPU the engine is running on.             *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' User Defined Types
'=================================================================================
TYPE CPUInfo                  ' Information about the CPU
    DEF Vendor:STRING         ' Vendor name
    DEF Name:STRING           ' CPU name
    DEF MMX:CHAR              ' Has MMX
    DEF SSE:CHAR              ' Has SSE
    DEF SSE2:CHAR             ' Has SSE2
ENDTYPE


'=================================================================================
' Global Variables
'=================================================================================
EXTERN gCPUInfo AS CPUInfo


'=================================================================================
' Functions
'=================================================================================
DECLARE EXTERN amCPUDetect()                            ' Get CPU info



Hootie

December 13, 2008, 12:27:21 PM #10 Last Edit: December 13, 2008, 11:03:21 PM by Hootie
Tutorial 8: Vector Functions

Lots of math goes on in a 3D game engine.  The next few code releases will be the parts of the math library.  At this time, I won't be going into the hows and whys of the various math functions.  I'll be going over that when we come to actually using them which will make things much clearer.

Here is the amVector4.eba and amVector4.inc that goes into the \math directory.


'****************************************************************************************
'*  amVector4.eba                                                                       *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: 4D vector math                                                         *
'*                                                                                      *
'*  This is a group of math functions for use with 4D vectors.                          *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' Includes
'=================================================================================
$INCLUDE ".\math\amVector4.inc"
$INCLUDE ".\machine\amCPU.inc"


'=================================================================================
' Source Global Variables
'=================================================================================
FLOAT sgFloat = 0.0
amVector4 sgVec4
FLOAT Tolerance = 0.0005   'Tolerance for comparisons


'=================================================================================
' amVector4Add()
'
' Add two 4D vectors
'
' Parameters:  v1 - Pointer to first vector
'              v2 - Pointer to second vector
'              v3 - Pointer to vector to store result of v1 + v2
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Add(POINTER v1, POINTER v2, POINTER v3)

#<amVector4>v3.X = #<amVector4>v1.X + #<amVector4>v2.X
#<amVector4>v3.Y = #<amVector4>v1.Y + #<amVector4>v2.Y
#<amVector4>v3.Z = #<amVector4>v1.Z + #<amVector4>v2.Z
#<amVector4>v3.W = 1.0

ENDSUB



'=================================================================================
' amVector4Subtract()
'
' Subtract two 4D vectors
'
' Parameters:  v1 - Pointer to first vector
'              v2 - Pointer to second vector
'              v3 - Pointer to vector to store result of v1 - v2
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Subtract(POINTER v1, POINTER v2, POINTER v3)

#<amVector4>v3.X = #<amVector4>v1.X - #<amVector4>v2.X
#<amVector4>v3.Y = #<amVector4>v1.Y - #<amVector4>v2.Y
#<amVector4>v3.Z = #<amVector4>v1.Z - #<amVector4>v2.Z
#<amVector4>v3.W = 1.0

ENDSUB



'=================================================================================
' amVector4Scale()
'
' Scale a 4D vector by a scalar
'
' Parameters:  v1 - Pointer to vector to scale
'              s1 - Value to scale vector with
'              v3 - Pointer to vector to store result of v1 * s1
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Scale(POINTER v1, FLOAT s1, POINTER v3)

#<amVector4>v3.X = #<amVector4>v1.X * s1
#<amVector4>v3.Y = #<amVector4>v1.Y * s1
#<amVector4>v3.Z = #<amVector4>v1.Z * s1
#<amVector4>v3.W = 1.0

ENDSUB



'=================================================================================
' amVector4Inverse()
'
' Invert a 4D vector
'
' Parameters:  v1 - Pointer to vector to inverse
'              v2 - Pointer to vector to store result of v1 * -1
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Inverse(POINTER v1, POINTER v2)

#<amVector4>v2.X = #<amVector4>v1.X * -1
#<amVector4>v2.Y = #<amVector4>v1.Y * -1
#<amVector4>v2.Z = #<amVector4>v1.Z * -1
#<amVector4>v2.W = 1.0

ENDSUB



'=================================================================================
' amVector4Length()
'
' Length of a 4D vector
'
' Parameters:  v1 - Pointer to vector to get length of
'             
'
' Returns: Length as FLOAT
'     
'=================================================================================
GLOBAL SUB amVector4Length(POINTER v1), FLOAT

IF gCPUInfo.SSE = 0
sgFloat = SQRT((#<amVector4>v1.X * #<amVector4>v1.X) + (#<amVector4>v1.Y * #<amVector4>v1.Y) + (#<amVector4>v1.Z * #<amVector4>v1.Z))
ELSE
#<amVector4>v1.W = 0.0
sgVec4 = #<amVector4>v1

_asm
movups xmm0, [$sgVec4]
mulps xmm0, xmm0
movaps xmm1, xmm0
shufps xmm1, xmm1, 0x4e
addps xmm0, xmm1
movaps xmm1, xmm0
shufps xmm1, xmm1, 0x11
addps xmm0, xmm1
sqrtss xmm0, xmm0
movss [$sgFloat], xmm0
_endasm

#<amVector4>v1.W = 1.0
ENDIF

RETURN sgFloat

ENDSUB



'=================================================================================
' amVector4Dot()
'
' Dot product of two 4D vectors
'
' Parameters:  v1 - Pointer to first vector
'              v2 - Pointer to second vector
'             
'
' Returns: Dot product as FLOAT
'     
'=================================================================================
GLOBAL SUB amVector4Dot(POINTER v1, POINTER v2), FLOAT

sgFloat = ((#<amVector4>v1.X * #<amVector4>v2.X) + (#<amVector4>v1.Y * #<amVector4>v2.Y) + (#<amVector4>v1.Z * #<amVector4>v2.Z))

RETURN sgFloat

ENDSUB



'=================================================================================
' amVector4Cross()
'
' Cross product of two 4D vectors
'
' Parameters:  v1 - Pointer to first vector
'              v2 - Pointer to second vector
'              v3 - Pointer to vector to store result of v1 cross v2
'             
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Cross(POINTER v1, POINTER v2, POINTER v3)

#<amVector4>v3.X = (#<amVector4>v1.Y * #<amVector4>v2.Z) - (#<amVector4>v1.Z * #<amVector4>v2.Y)
#<amVector4>v3.Y = (#<amVector4>v1.Z * #<amVector4>v2.X) - (#<amVector4>v1.X * #<amVector4>v2.Z)
#<amVector4>v3.Z = (#<amVector4>v1.X * #<amVector4>v2.Y) - (#<amVector4>v1.Y * #<amVector4>v2.X)
#<amVector4>v3.W = 1.0

ENDSUB



'=================================================================================
' amVector4Normalize()
'
' Normalize a 4D vector
'
' Parameters:  v1 - Pointer to vector to normalize
'              v2 - Pointer to vector to store v1 normalized
'             
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Normalize(POINTER v1, POINTER v2)

IF gCPUInfo.SSE = 0
sgFloat = amVector4Length(v1)
#<amVector4>v2.X = #<amVector4>v1.X / sgFloat
#<amVector4>v2.Y = #<amVector4>v1.Y / sgFloat
#<amVector4>v2.Z = #<amVector4>v1.Z / sgFloat
#<amVector4>v2.W = 1.0
ELSE
sgVec4 = #<amVector4>v1
sgVec4.W = 0.0

_asm
movups xmm0, [$sgVec4]
movaps xmm2, xmm0
mulps  xmm0, xmm0
movaps xmm1, xmm0
shufps xmm0, xmm1, 0x4e
addps  xmm0, xmm1
movaps xmm1, xmm0
shufps xmm1, xmm1, 0x11
addps  xmm0, xmm1
rsqrtps xmm0, xmm0
mulps  xmm2, xmm0
movups [$sgVec4], xmm2
_endasm

sgVec4.W = 1.0
#<amVector4>v2 = sgVec4
ENDIF

ENDSUB



'=================================================================================
' amVector4Compare()
'
' Compare two 4D vectors with tolerance
'
' Parameters:  v1 - Pointer to first vector
'              v2 - Pointer to second vector
'             
'
' Returns: 0 - <>  1 - = as INT
'     
'=================================================================================
GLOBAL SUB amVector4Compare(POINTER v1, POINTER v2), INT

IF ABS(#<amVector4>v1.X - #<amVector4>v2.X) > Tolerance
RETURN 0
ENDIF

IF ABS(#<amVector4>v1.Y - #<amVector4>v2.Y) > Tolerance
RETURN 0
ENDIF

IF ABS(#<amVector4>v1.Z - #<amVector4>v2.Z) > Tolerance
RETURN 0
ENDIF

RETURN 1

ENDSUB



'=================================================================================
' amVector4Lerp()
'
' Linear interpolation between two 4D vectors
'
' Parameters:  v1 - Pointer to first vector
'              v2 - Pointer to second vector
'              s1 - Interpolater
'              v3 - Pointer to interpolated vector between v1 and v2
'             
'
' Returns: Nothing
'     
'=================================================================================
GLOBAL SUB amVector4Lerp(POINTER v1, POINTER v2, FLOAT s1, POINTER v3)

#<amVector4>v3.X = #<amVector4>v1.X + ((#<amVector4>v2.X - #<amVector4>v1.X) * s1)
#<amVector4>v3.Y = #<amVector4>v1.Y + ((#<amVector4>v2.Y - #<amVector4>v1.Y) * s1)
#<amVector4>v3.Z = #<amVector4>v1.Z + ((#<amVector4>v2.Z - #<amVector4>v1.Z) * s1)
#<amVector4>v3.W = 1.0

ENDSUB




'****************************************************************************************
'*  amVector4.inc                                                                      *
'*                                                                                      *
'*  Author: Mark O'Hara                                                                 *
'*  Description: 4D vector math                                                         *
'*                                                                                      *
'*  This is a group of math functions for use with 4D vectors.                          *
'*                                                                                      *
'*                                                                                      *
'*                                                                                      *
'*  Copyright (C) 2008, 2009 Cheyenne Cloud, LLC. All Rights Reserved.                  *
'*  Use of this code is subject to the Ani-Mates Source Code License.                   *
'****************************************************************************************


'=================================================================================
' User Defined Types
'=================================================================================
TYPE amVector4          ' 4D vector
    DEF X:FLOAT         
    DEF Y:FLOAT         
    DEF Z:FLOAT         
    DEF W:FLOAT         
ENDTYPE


'=================================================================================
' Functions
'=================================================================================
DECLARE EXTERN amVector4Add(POINTER v1, POINTER v2, POINTER v3)            ' Add two 4D vectors
DECLARE EXTERN amVector4Subtract(POINTER v1, POINTER v2, POINTER v3)       ' Subtract two 4D vectors
DECLARE EXTERN amVector4Scale(POINTER v1, FLOAT s1, POINTER v3)            ' Scale a 4D vector by a scalar
DECLARE EXTERN amVector4Inverse(POINTER v1, POINTER v2)                    ' Inverse a 4D vector
DECLARE EXTERN amVector4Length(POINTER v1), FLOAT                          ' Get length of a 4D vector
DECLARE EXTERN amVector4Dot(POINTER v1, POINTER v2), FLOAT                 ' Dot Product of two 4D vectors
DECLARE EXTERN amVector4Cross(POINTER v1, POINTER v2, POINTER v3)          ' Cross Product of two 4D vectors
DECLARE EXTERN amVector4Normalize(POINTER v1, POINTER v2)                  ' Normalize a 4D vector
DECLARE EXTERN amVector4Compare(POINTER v1, POINTER v2), INT               ' Compare two 4D vectors with tolerance
DECLARE EXTERN amVector4Lerp(POINTER v1, POINTER v2, FLOAT s1, POINTER v3) ' Linear interpolation between two 4D vectors



LarryMc

Hootie

Looking forward to more installments.

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

Hootie

Quote from: Larry McCaughn on March 22, 2009, 06:43:31 PM
Looking forward to more installments.

Unfortunately I have been drug away to work on some iPhone games.  I don't know if/when I'll be able to get back to this.  I apologize for having to leave this undone.