Author Topic: Yet Another Way To Code Lr... (finished Product)  (Read 2159 times)

Offline Flashy

  • Sr. Member
  • ****
  • Posts: 391
Yet Another Way To Code Lr... (finished Product)
« on: November 20, 2010, 01:49:38 PM »
I wrote a new way to create landrush layouts to be able to create new missions faster. (except for the starting locations) (Based on Sirbombers really helpful coding tutorials, thanks Sirbomber)

Will it be adequate for my next mission?

Code: [Select]
// Used below to randomize bases
#define numof(array) (sizeof(array)/sizeof(array[0]))
#define autosize(array) numof(array), array

#include "LandRushCreator.h"

// Randomly assign start locations to the players
void RandomizeStartLocations()
{
    // Randomize the list of all possible start locations
    int allStarts[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    RandomizeList(autosize(allStarts) );

Location startLoc[] = {
  {171 -1, 178 - 1},
  {118 -1, 67 -  1},
  {441 -1, 187 -  1},
  {457  -1, 19 -  1},
  {329  -1, 23 -  1},
  {56  -1, 191 -  1},
  {56  -1, 191 -  1},
  {56  -1, 191 -  1},
  {56  -1, 191 -  1},
  {56  -1, 191 -  1}
};

    // Go through each active player
    for (int s = 0; s < TethysGame::NoPlayers(); s++)
    {
        LandRushCreator rushClass;
        //Uncomment the next line to setup other starting resources
        //rushClass.customRes = true;

        // Assign them their corresponding start location from the list
        rushClass.CreateFullLandRush(s, startLoc[allStarts[s]].x, startLoc[allStarts[s]].y);

    }
} // end RandomizeStartLocations
I can use the finished pattern CreateFullLandRush, or another function like it's shown in CreateFullLandRush
Code: [Select]
void CreateCustomLandRush(int s, int xPos, int yPos)
{
        // Center view on start location
        Player[s].CenterViewOn(xPos, yPos);
        LandRushCreator rushClass;
        rushClass.Init(xPos, yPos, s);
        rushClass.SetupLandRushResources();

        rushClass.CreateRush(mapRoboSurveyor, -3, -3);
        rushClass.CreateRush(mapCargoTruck, 0, 2);
        rushClass.SetFood();
        rushClass.CreateRush(mapConVec, -2, -2 // .......
« Last Edit: November 22, 2010, 02:12:32 PM by Flashy »
Praise the mighty light towers!!!

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Yet Another Way To Code Lr... (finished Product)
« Reply #1 on: November 20, 2010, 09:44:03 PM »
From a cursory glance I don't see any obvious issues with the code. Although, I do have two small suggestions for slight code improvements.


The smaller change idea would be to change the declaration of "s" from a short to an int (or possibly even an unsigned char).

This is simply a (very minor) efficiency improvement. I know you don't need the full range of an int, but due to data alignment issues, either data type will take up 4 bytes of data storage. In the case of the short, those extra two bytes just become useless padding bytes. Also, since x86 code runs 32 bit natively, it takes a special opcode prefix for every instruction processing the data to switch to 16 bit processing, which takes up extra space in the code segment and more time to execute the instruction. So, shorts end up being the same data size, but produce larger and slower code. Basically, you should stay away from shorts for x86 code unless you either need to be compatible with something else, or you have an array of data (which has no padding between elements), and hopefully don't do a lot of processing on the data.

Btw, 8-bit values can usually be processed just as easily as 32 bit values (without an opcode prefix). Although, if they are used in an address calculation, such as array indexing, they must be promoted to 32 bits, which may take an extra instruction. Data alignment issues also apply to these, which may cause a single variable to also take up 4 bytes instead of just 1.

Some of that alignment padding can be eliminated by packing data types of similar sizes together so that none of them cross a 4 byte boundary. You would of course have to declare a number of variables with a data type smaller than 4 bytes. In theory an optimizing compiler could do this. In practice I've never seen the MSVC compiler do this except for struct declarations. In other words, freely declared variables such as you've used will never do this under MSVC.

Also, the code size expansion from using 16 bit data types may not always be noticable due to code section padding/alignment which may round the size of the code section up. This is usually done in 4KB increments. So using a short might just will more of a partially empty 4KB code page, or it might cross a boundary and cause the exe size to jump up by 4KB. The speed issue from having to process the opcode prefix still applies in any case.



The other suggestion is to replace the switch statement with an array lookup. If you notice your case values form a nice compact set of integers that could easily be array indexes. Further, the only change in the code from one case to the next is the numeric values being passed to the function being called. Instead, you could refactor the code so those values are placed in an array (probably an array of structs, where each struct contains an x and y value), and simply to a lookup in that array rather than use a case statement. The code will be far smaller and easier to maintain. It should also produce more space efficient machine code.

I'll take a stab at writing untested code as an example. Just don't be surprised if it doesn't compile.

Code: [Select]
struct Location
{
  int x;
  int y;
};

// Declare starting location data in an array of structs
Location startLocation[] = {
  {30, 91},
  {98, 38},
  {30, 30},
  // ...
};


// ...
// Modified code
  // Go through each active player
  for (int s = 0; s < TethysGame::NoPlayers(); s++)
  {
    LandRushCreator rushClass;
    //Uncomment the next line to setup other starting resources
    //rushClass.customRes = true;
    
    // Get start coordinates with simple array lookup
    location = startLocation[s]
    rushClass.CreateFullLandRush(s, location.x + 31, location.y - 1);
  }

I usually avoid case statements. I find whenever they are used, a more compact data driven solution is usually possible. Either that, or I find myself using virtual functions for some class hierarchy, which is pretty similar, but again, less code and essentially backed by a table based lookup scheme.

 

Offline Flashy

  • Sr. Member
  • ****
  • Posts: 391
Yet Another Way To Code Lr... (finished Product)
« Reply #2 on: November 22, 2010, 02:05:34 PM »
Post 1)
Ok, thank you, I'll do that.
~~~~~~~~~
Post 2)
...Found a mistake miself... I forgot bool isTank=false; in CreateRush(map_id,int,int,map_id)
~~~~~~~~~
Post 3)
I'm using the switch for LoS, though it calls different functions for each case, so I can't use the same method. What do you suggest? Function pointer arrays maybe? Or leaving it be?
(Though I think i should use no more than one function next time)
« Last Edit: November 22, 2010, 02:05:49 PM by Flashy »
Praise the mighty light towers!!!

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Yet Another Way To Code Lr... (finished Product)
« Reply #3 on: November 23, 2010, 12:09:19 AM »
I don't often use function pointers, at least not directly. Normally I have more object oriented code where a virtual member function works nicely. Internally they're implemented using a table of function pointers, but you don't specify that anywhere in the syntax of the source code.

If you'd like an example of the concept:
Code: [Select]
// Define an "interface" class that declares the function prototypes available for each derived class.
class BaseInterface
{
  // Declare virtual functions that must be present in every derived class
  // Declare any functions as "pure virtual" ("= 0" at the end) if there is no default implementation.
  // Note: Pure virtual declarations will get rid of compiler errors about missing function bodies (that should never exist in the first place).
  virtual void Create(int x, int y /*, ... other parameters */) = 0;
  // ... any other functions that need to be present on all derived classes
};

// Declare derived classes
// Note the ": public BaseInterface" to specify it inherits all functions in the interface
class BaseOnly : public BaseInterface
{
  // If you don't expect more classes to derive from this one, you can drop the "virtual".
  // For a "concrete" class that you can create, such as with "new", there must be no pure virtual functions ("= 0")
  void Create(int x, int y);

  // You can declare addition member functions that are not inherited
  // ...
};

class BaseWithVehicles : public BaseInterface
{
  void Create(int x, int y);

  // You can declare addition member functions that are not inherited
  // ...
  void TurnOnAllLights();
};


void BaseOnly::Create(int x, int y)
{
  // Create the base
  // ...
}


void BaseWithVehicles::Create(int x, int y)
{
  // Create the base
  // ...

  // Create the vehicles
  // ...

  // Initialize lights? Create unit groups? Setup mining groups?
  // ...
}




// Example usage:


// Example data structure declaration
struct BaseLayout
{
  int x;
  int y;
  // A pointer to a base class can actually refer to a derived class
  // This is where virtual functions are most useful.
  BaseInterface* base;
};

BaseOnly simpleBase(/*Constructor parameters*/);
BaseWithVehicles vehicleBase(/*Constructor parameters*/);

BaseLayout baseLayout[] = {
  {60, 50, &simpleBase},
  {90, 30, &vehicleBase},
};


// Example function that might take such a data structure
void CreateNumBases(int numBases, BaseLayout baseLayout[])
{
  // Randomize baseList
  // ...

  for (int i = 0; i < numBases; ++i)
  {
    // Grab a reference to the desired array element
    BaseLayout &layout = baseLayout[i];
    // **This is where the magic happens**
    // Note: A single call here, will dispath to either of the derived implementations, depending on the actual type of the class
    // This is done by calling the function through the virtual function table of the class
    // This adds a slight overhead to the call, and size of the class objects, but offers an awful lot of flexibility
    layout.base->Create(layout.x, layout.y);
  }
}



Note that this can be a little heavy on the syntax and general setup, so function pointers might be more appropriate for what you're doing. This sort of code becomes more useful as you increase the number of functions that switch along with the class type. It moves the logic of the "switch" statements from the point of call into the structure of the class hierarchy. It also allows new functionality to be easily added to an existing class, or even new classes to be added after the code has been written that would need to call these functions, and without any updates to it. This is particularly useful if there are many points in the source code that call the virtual functions.


I'm not entirely sure if this would be appropriate for you. Maybe experiment with the concept, or post an update to what you have so I have a little more to comment on.

 
« Last Edit: November 23, 2010, 12:14:23 AM by Hooman »

Offline Flashy

  • Sr. Member
  • ****
  • Posts: 391
Yet Another Way To Code Lr... (finished Product)
« Reply #4 on: November 23, 2010, 11:54:34 AM »
That looks really nice, thanks for your help. I'll look what I can do about my existing mission, but i think function pointers would be easier, since they don't require any rewriting.
Praise the mighty light towers!!!