mirror of
https://github.com/TerryCavanagh/VVVVVV.git
synced 2024-06-23 21:18:29 +02:00
83976016c7
Note that level dir listing still uses plenty of STL (including the end product - the `LevelMetaData` struct - which, for the purposes of 2.3, is okay enough (2.4 should remove STL usage entirely)); it's just that the initial act of iterating over the levels directory no longer takes four or SIX(!!!) heap allocations (not counting reallocations and other heap allocations this patch does not remove), and no longer does any data marshalling. Like text splitting, and binary blob extra indice grabbing, the current approach that FILESYSTEM_getLevelDirFileNames() uses is a temporary std::vector of std::strings as a middleman to store all the filenames, and the game iterates over that std::vector to grab each level metadata. Except, it's even worse in this case, because PHYSFS_enumerateFiles() ALREADY does a heap allocation. Oh, and FILESYSTEM_getLevelDirFileNames() gets called two or three times. Yeah, let me explain: 1. FILESYSTEM_getLevelDirFileNames() calls PHYSFS_enumerateFiles(). 2. PHYSFS_enumerateFiles() allocates an array of pointers to arrays of chars on the heap. For each filename, it will: a. Allocate an array of chars for the filename. b. Reallocate the array of pointers to add the pointer to the above char array. (In this step, it also inserts the filename in alphabetically - without any further allocations, as far as I know - but this is a COMPLETELY unnecessary step, because we are going to sort the list of levels by ourselves via the metadata title in the end anyways.) 3. FILESYSTEM_getLevelDirFileNames() iterates over the PhysFS list, and allocates an std::vector on the heap to shove the list into. Then, for each filename, it will: a. Allocate an std::string, initialized to "levels/". b. Append the filename to the std::string above. This will most likely require a re-allocation. c. Duplicate the std::string - which requires allocating more memory again - to put it into the std::vector. (Compared to the PhysFS list above, the std::vector does less reallocations; it however will still end up reallocating a certain amount of times in the end.) 4. FILESYSTEM_getLevelDirFileNames() will free the PhysFS list. 5. Then to get the std::vector<std::string> back to the caller, we end up having to reallocate the std::vector again - reallocating every single std::string inside it, too - to give it back to the caller. And to top it all off, FILESYSTEM_getLevelDirFileNames() is guaranteed to either be called two times, or three times. This is because editorclass::getDirectoryData() will call editorclass::loadZips(), which will unconditionally call FILESYSTEM_getLevelDirFileNames(), then call it AGAIN if a zip was found. Then once the function returns, getDirectoryData() will still unconditionally call FILESYSTEM_getLevelDirFileNames(). This smells like someone bolting something on without regard for the whole picture of the system, but whatever; I can clean up their mess just fine. So, what do I do about this? Well, just like I did with text splitting and binary blob extras, make the final for-loop - the one that does the actual metadata parsing - more immediate. So how do I do that? Well, PhysFS has a function named PHYSFS_enumerate(). PHYSFS_enumerateFiles(), in fact, uses this function internally, and is basically just a wrapper with some allocation and alphabetization. PHYSFS_enumerate() takes in a pointer to a function, which it will call for every single entry that it iterates over. It also lets you pass in another arbitrary pointer that it leaves alone, which I use to pass through a function pointer that is the actual callback. So to clarify, there are two callbacks - one callback is passed through into another callback that gets passed through to PHYSFS_enumerate(). The callback that gets passed to PHYSFS_enumerate() is always the same, but the callback that gets passed through the callback can be different (if you look at the calling code, you can see that one caller passes through a normal level metadata callback; the other passes through a zip file callback). Furthermore, I've also cleaned it up so that if editorclass::loadZips() finds a zip file, it won't iterate over all the files in the levels directory a third time. Instead, the level directory only gets iterated over twice - once to check for zips, and another to load every level plus all zips; the second time is when all the heap allocations happen. And with that, level list loading now uses less STL templated stuff and much less heap allocations. Also, ed.directoryList basically has no reason to exist other than being a temporary std::vector, so I've removed it. This further decreases memory usage, depending on how many levels you have in your levels folder (I know that I usually have a lot and don't really ever clean it up, lol). Lastly, in the callback passed to PhysFS, `builtLocation` is actually no longer hardcoded to just the `levels` directory, since instead we now use the `origdir` variable that PhysFS passes us. So that's good, too.
284 lines
5.8 KiB
C++
284 lines
5.8 KiB
C++
#if !defined(NO_CUSTOM_LEVELS)
|
|
|
|
#ifndef EDITOR_H
|
|
#define EDITOR_H
|
|
|
|
#include <SDL.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Text entry field type
|
|
enum textmode {
|
|
TEXT_NONE,
|
|
|
|
// In-editor text fields
|
|
TEXT_LOAD,
|
|
TEXT_SAVE,
|
|
TEXT_ROOMNAME,
|
|
TEXT_SCRIPT,
|
|
TEXT_ROOMTEXT,
|
|
TEXT_GOTOROOM,
|
|
LAST_EDTEXT = TEXT_GOTOROOM,
|
|
|
|
// Settings-mode text fields
|
|
TEXT_TITLE,
|
|
TEXT_DESC,
|
|
TEXT_WEBSITE,
|
|
TEXT_CREATOR,
|
|
NUM_TEXTMODES,
|
|
|
|
// Text modes with an entity
|
|
FIRST_ENTTEXT = TEXT_SCRIPT,
|
|
LAST_ENTTEXT = TEXT_ROOMTEXT
|
|
};
|
|
|
|
class edentities{
|
|
public:
|
|
int x, y, t;
|
|
//parameters
|
|
int p1, p2, p3, p4, p5, p6;
|
|
std::string scriptname;
|
|
};
|
|
|
|
|
|
class edlevelclass{
|
|
public:
|
|
edlevelclass();
|
|
int tileset, tilecol;
|
|
std::string roomname;
|
|
int warpdir;
|
|
int platx1, platy1, platx2, platy2, platv;
|
|
int enemyx1, enemyy1, enemyx2, enemyy2, enemytype;
|
|
int directmode;
|
|
};
|
|
|
|
struct LevelMetaData
|
|
{
|
|
std::string title;
|
|
std::string creator;
|
|
std::string Desc1;
|
|
std::string Desc2;
|
|
std::string Desc3;
|
|
std::string website;
|
|
std::string filename;
|
|
|
|
std::string modifier;
|
|
std::string timeCreated;
|
|
std::string timeModified;
|
|
|
|
int version;
|
|
};
|
|
|
|
|
|
extern std::vector<edentities> edentity;
|
|
|
|
class EditorData
|
|
{
|
|
public:
|
|
|
|
static EditorData& GetInstance()
|
|
{
|
|
static EditorData instance; // Guaranteed to be destroyed.
|
|
// Instantiated on first use.
|
|
return instance;
|
|
}
|
|
|
|
|
|
std::string title;
|
|
std::string creator;
|
|
|
|
std::string modifier;
|
|
};
|
|
|
|
struct GhostInfo {
|
|
int rx; // game.roomx-100
|
|
int ry; // game.roomy-100
|
|
int x; // .xp
|
|
int y; // .yp
|
|
int col; // .colour
|
|
Uint32 realcol;
|
|
int frame; // .drawframe
|
|
};
|
|
|
|
class editorclass{
|
|
//Special class to handle ALL editor variables locally
|
|
public:
|
|
editorclass();
|
|
|
|
std::string Desc1;
|
|
std::string Desc2;
|
|
std::string Desc3;
|
|
std::string website;
|
|
|
|
std::vector<LevelMetaData> ListOfMetaData;
|
|
|
|
void loadZips();
|
|
void getDirectoryData();
|
|
bool getLevelMetaData(std::string& filename, LevelMetaData& _data );
|
|
|
|
void reset();
|
|
void getlin(const enum textmode mode, const std::string& prompt, std::string* ptr);
|
|
const short* loadlevel(int rxi, int ryi);
|
|
|
|
void placetilelocal(int x, int y, int t);
|
|
|
|
int getenemyframe(int t);
|
|
int base(int x, int y);
|
|
|
|
int backbase(int x, int y);
|
|
|
|
int at(int x, int y);
|
|
|
|
int freewrap(int x, int y);
|
|
|
|
int backonlyfree(int x, int y);
|
|
|
|
int backfree(int x, int y);
|
|
|
|
int spikefree(int x, int y);
|
|
int free(int x, int y);
|
|
int absfree(int x, int y);
|
|
|
|
int match(int x, int y);
|
|
int outsidematch(int x, int y);
|
|
|
|
int backmatch(int x, int y);
|
|
|
|
void switch_tileset(const bool reversed = false);
|
|
void switch_tilecol(const bool reversed = false);
|
|
void clamp_tilecol(const int rx, const int ry, const bool wrap = false);
|
|
void switch_enemy(const bool reversed = false);
|
|
|
|
bool load(std::string& _path);
|
|
bool save(std::string& _path);
|
|
void generatecustomminimap();
|
|
int edgetile(int x, int y);
|
|
int outsideedgetile(int x, int y);
|
|
|
|
int backedgetile(int x, int y);
|
|
|
|
int labspikedir(int x, int y, int t);
|
|
int spikedir(int x, int y);
|
|
int findtrinket(int t);
|
|
int findcrewmate(int t);
|
|
int findwarptoken(int t);
|
|
void findstartpoint();
|
|
int getlevelcol(int t);
|
|
int getenemycol(int t);
|
|
int entcol;
|
|
Uint32 entcolreal;
|
|
|
|
//Colouring stuff
|
|
int getwarpbackground(int rx, int ry);
|
|
|
|
std::vector<std::string> getLevelDirFileNames( );
|
|
static const int maxwidth = 20, maxheight = 20; //Special; the physical max the engine allows
|
|
static const int numrooms = maxwidth * maxheight;
|
|
short contents[40 * 30 * numrooms];
|
|
int vmult[30 * maxheight];
|
|
int numtrinkets();
|
|
int numcrewmates();
|
|
edlevelclass level[numrooms]; //Maxwidth*maxheight
|
|
int kludgewarpdir[numrooms]; //Also maxwidth*maxheight
|
|
|
|
int notedelay;
|
|
int oldnotedelay;
|
|
std::string note;
|
|
std::string keybuffer;
|
|
std::string filename;
|
|
std::string loaded_filepath;
|
|
|
|
int drawmode;
|
|
int tilex, tiley;
|
|
int keydelay, lclickdelay;
|
|
bool savekey, loadkey;
|
|
int levx, levy;
|
|
int entframe, entframedelay;
|
|
|
|
int scripttexttype;
|
|
std::string oldenttext;
|
|
|
|
enum textmode textmod; // In text entry
|
|
std::string* textptr; // Pointer to text we're changing
|
|
std::string textdesc; // Description (for editor mode text fields)
|
|
union {
|
|
int desc; // Which description row we're changing
|
|
int textent; // Entity ID for text prompt
|
|
};
|
|
bool xmod, zmod, cmod, vmod, bmod, hmod, spacemod, warpmod;
|
|
bool titlemod, creatormod, desc1mod, desc2mod, desc3mod, websitemod;
|
|
|
|
int roomnamehide;
|
|
bool saveandquit;
|
|
bool shiftmenu, shiftkey;
|
|
int spacemenu;
|
|
bool settingsmod, settingskey;
|
|
int warpent;
|
|
bool updatetiles, changeroom;
|
|
int deletekeyheld;
|
|
|
|
int boundarymod, boundarytype;
|
|
int boundx1, boundx2, boundy1, boundy2;
|
|
|
|
int levmusic;
|
|
int mapwidth, mapheight; //Actual width and height of stage
|
|
|
|
int version;
|
|
|
|
//Script editor stuff
|
|
void removeline(int t);
|
|
void insertline(int t);
|
|
|
|
bool scripteditmod;
|
|
int scripthelppage, scripthelppagedelay;
|
|
std::vector<std::string> sb;
|
|
std::string sbscript;
|
|
int sbx, sby;
|
|
int pagey;
|
|
|
|
//Functions for interfacing with the script:
|
|
void addhook(std::string t);
|
|
void removehook(std::string t);
|
|
void addhooktoscript(std::string t);
|
|
void removehookfromscript(std::string t);
|
|
void loadhookineditor(std::string t);
|
|
void clearscriptbuffer();
|
|
void gethooks();
|
|
bool checkhook(std::string t);
|
|
std::vector<std::string> hooklist;
|
|
|
|
int hookmenupage, hookmenu;
|
|
|
|
//Direct Mode variables
|
|
int dmtile;
|
|
int dmtileeditor;
|
|
|
|
Uint32 getonewaycol(const int rx, const int ry);
|
|
Uint32 getonewaycol();
|
|
bool onewaycol_override;
|
|
|
|
int returneditoralpha;
|
|
int oldreturneditoralpha;
|
|
|
|
std::vector<GhostInfo> ghosts;
|
|
int currentghosts;
|
|
};
|
|
|
|
#if !defined(NO_EDITOR)
|
|
void editorrender();
|
|
|
|
void editorrenderfixed();
|
|
|
|
void editorlogic();
|
|
|
|
void editorinput();
|
|
#endif
|
|
|
|
#ifndef ED_DEFINITION
|
|
extern editorclass ed;
|
|
#endif
|
|
|
|
#endif /* EDITOR_H */
|
|
|
|
#endif /* NO_CUSTOM_LEVELS */
|