2020-01-01 21:29:24 +01:00
|
|
|
#ifndef UTILITYCLASS_H
|
|
|
|
#define UTILITYCLASS_H
|
|
|
|
|
|
|
|
#include <SDL.h>
|
|
|
|
#include <string>
|
|
|
|
|
2021-02-07 22:09:47 +01:00
|
|
|
int ss_toi(const std::string& str);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
bool next_split(
|
|
|
|
size_t* start,
|
|
|
|
size_t* len,
|
|
|
|
const char* str,
|
|
|
|
const char delim
|
|
|
|
);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Refactor loading arrays from XML to not use the STL
The current way "arrays" from XML files are loaded (before this commit
is applied) goes something like this:
1. Read the buffer of the contents of the tag using TinyXML-2.
2. Allocate a buffer on the heap of the same size, and copy the
existing buffer to it. (This is what the statement `std::string
TextString = pText;` does.)
3. For each delimiter in the heap-allocated buffer...
a. Allocate another buffer on the heap, and copy the characters from
the previous delimiter to the delimiter you just hit.
b. Then allocate the buffer AGAIN, to copy it into an std::vector.
4. Then re-allocate every single buffer YET AGAIN, because you need to
make a copy of the std::vector in split() to return it to the caller.
As you can see, the existing way uses a lot of memory allocations and
data marshalling, just to split some text.
The problem here is mostly making a temporary std::vector of split text,
before doing any actual useful work (most likely, putting it into an
array or ANOTHER std::vector - if the latter, then that's yet another
memory allocation on top of the memory allocation you already did; this
memory allocation is unavoidable, unlike the ones mentioned earlier,
which should be removed).
So I noticed that since we're iterating over the entire string once
(just to shove its contents into a temporary std::vector), and then
basically iterating over it again - why can't the whole thing just be
more immediate, and just be iterated over once?
So that's what I've done here. I've axed the split() function (both of
them, actually), and made next_split() and next_split_s().
next_split() will take an existing string and a starting index, and it
will find the next occurrence of the given delimiter in the string. Once
it does so, it will return the length from the previous starting index,
and modify your starting index as well. The price for immediateness is
that you're supposed to handle keeping the index of the previous
starting index around in order to be able to use the function; updating
it after each iteration is also your responsibility.
(By the way, next_split() doesn't use SDL_strchr(), because we can't get
the length of the substring for the last substring. We could handle this
special case specifically, but it'd be uglier; it also introduces
iterating over the last substring twice, when we only need to do it
once.)
next_split_s() does the same thing as next_split(), except it will copy
the resulting substring into a buffer that you provide (along with its
size). Useful if you don't particularly care about the length of the
substring.
All callers have been updated accordingly. This new system does not make
ANY heap allocations at all; at worst, it allocates a temporary buffer
on the stack, but that's only if you use next_split_s(); plus, it'd be a
fixed-size buffer, and stack allocations are negligible anyway.
This improves performance when loading any sort of XML file, especially
loading custom levels - which, on my system at least, I can noticeably
tell (there's less of a freeze when I load in to a custom level with
lots of scripts). It also decreases memory usage, because the heap isn't
being used just to iterate over some delimiters when XML files are
loaded.
2021-02-13 01:37:29 +01:00
|
|
|
bool next_split_s(
|
|
|
|
char buffer[],
|
|
|
|
const size_t buffer_size,
|
|
|
|
size_t* start,
|
|
|
|
const char* str,
|
|
|
|
const char delim
|
|
|
|
);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-08-07 06:22:10 +02:00
|
|
|
bool is_number(const char* str);
|
|
|
|
|
2021-02-12 01:36:22 +01:00
|
|
|
bool is_positive_num(const char* str, const bool hex);
|
2020-04-17 23:52:09 +02:00
|
|
|
|
2021-02-27 00:29:37 +01:00
|
|
|
bool endsWith(const char* str, const char* suffix);
|
2020-06-21 23:25:23 +02:00
|
|
|
|
2021-04-12 02:19:53 +02:00
|
|
|
void VVV_fillstring(
|
|
|
|
char* buffer,
|
|
|
|
const size_t buffer_size,
|
|
|
|
const char fillchar
|
|
|
|
);
|
|
|
|
|
2020-09-08 09:31:44 +02:00
|
|
|
#define INBOUNDS_VEC(index, vector) ((int) index >= 0 && (int) index < (int) vector.size())
|
2020-07-03 04:17:32 +02:00
|
|
|
#define INBOUNDS_ARR(index, array) ((int) index >= 0 && (int) index < (int) SDL_arraysize(array))
|
2020-06-14 03:22:05 +02:00
|
|
|
|
2020-07-01 00:01:30 +02:00
|
|
|
#define WHINE_ONCE(message) \
|
|
|
|
static bool whine = true; \
|
|
|
|
if (whine) \
|
|
|
|
{ \
|
|
|
|
whine = false; \
|
2021-02-24 00:21:29 +01:00
|
|
|
vlog_error(message); \
|
2021-08-07 06:05:03 +02:00
|
|
|
} \
|
|
|
|
do { } while (false)
|
2020-07-01 00:01:30 +02:00
|
|
|
|
2023-01-07 19:28:07 +01:00
|
|
|
#define WHINE_ONCE_ARGS(args) \
|
|
|
|
static bool whine = true; \
|
|
|
|
if (whine) \
|
|
|
|
{ \
|
|
|
|
whine = false; \
|
|
|
|
vlog_error args; \
|
|
|
|
} \
|
|
|
|
do { } while (false)
|
|
|
|
|
2021-05-20 23:14:08 +02:00
|
|
|
/* Don't call this directly; use the VVV_between macro. */
|
|
|
|
void _VVV_between(
|
|
|
|
const char* original,
|
|
|
|
const size_t left_length,
|
|
|
|
char* middle,
|
|
|
|
const size_t right_length,
|
|
|
|
const size_t middle_size
|
|
|
|
);
|
|
|
|
|
|
|
|
/* If original is "LEFTMIDDLERIGHT", VVV_between(original, "LEFT", buffer, "RIGHT")
|
|
|
|
* will put "MIDDLE" into buffer - assuming that sizeof(buffer) refers to length
|
|
|
|
* of buffer and not length of pointer to buffer.
|
|
|
|
*/
|
|
|
|
#define VVV_between(original, left, middle, right) \
|
|
|
|
_VVV_between( \
|
|
|
|
original, \
|
|
|
|
SDL_arraysize(left) - 1, \
|
|
|
|
middle, \
|
|
|
|
SDL_arraysize(right) - 1, \
|
|
|
|
sizeof(middle) \
|
|
|
|
)
|
|
|
|
|
2021-08-07 05:57:34 +02:00
|
|
|
#define MAYBE_FAIL(expr) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
if (!expr) \
|
|
|
|
{ \
|
|
|
|
goto fail; \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
while (false)
|
2020-01-01 21:29:24 +01:00
|
|
|
|
Add `POS_MOD` macro and use for all positive modulos
This macro is to make it so it won't be error-prone to write the
semi-confusing `(a % b + b) % b` statement, and you can just use an easy
macro instead.
Currently, the only places a positive modulo is needed is when switching
tilesets, enemies, and warp directions in the editor, as well as when
getting a tile in the tower, since towers just repeat themselves
vertically. Towers used this weird while-loop to sort of emulate a
modulo, which isn't half-bad, but is unnecessary, and I don't think any
compiler would recognize it as a modulo. (And if it's not optimized to a
proper modulo... what happens if the number being moduloed is really,
really big?)
2021-09-25 02:48:15 +02:00
|
|
|
/* Positive modulo, because C/C++'s modulo operator sucks and is negative given
|
|
|
|
* negative divisors.
|
|
|
|
* WARNING! This double- and triple- evaluates. */
|
|
|
|
#define POS_MOD(a, b) (((a) % (b) + (b)) % (b))
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
//helperClass
|
|
|
|
class UtilityClass
|
|
|
|
{
|
|
|
|
public:
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
UtilityClass(void);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
static std::string String(int _v);
|
|
|
|
|
2020-08-07 06:56:35 +02:00
|
|
|
static int Int(const char* str, int fallback = 0);
|
2020-08-07 06:28:51 +02:00
|
|
|
|
Simplify time formatting functions
Here's my notes on all the existing functions and what kind of time
formats they output:
- Game::giventimestring(int hrs, int min, int sec)
H:MM:SS
MM:SS
- Game::timestring()
// uses game.hours/minutes/seconds
H:MM:SS
MM:SS
- Game::partimestring()
// uses game.timetrialpar (seconds)
MM:SS
- Game::resulttimestring()
// uses game.timetrialresulttime (sec) + timetrialresultframes (1/30s)
MM:SS.CC
- Game::timetstring(int t)
// t = seconds
MM:SS
- Game::timestringcenti(char* buffer, const size_t buffer_size)
// uses game.hours/minutes/seconds/frames
H:MM:SS.CC
MM:SS.CC
- UtilityClass::timestring(int t)
// t = frames, 30 frames = 1 second
S:CC
M:SS:CC
This is kind of a mess, and there's a lot of functions that do the same
thing except using different variables. For localization, I also want
translators to be able to localize all these time formats - many
languages use the decimal comma instead of the decimal point (12:34,56)
maybe some languages really prefer something like 1時02分11秒44瞬...
Which I don't know to be correct, but it's good to be prepared for it
and not restrict translators arbitrarily to only changing ":" and "."
when we can start making the system better in the first place.
I added a new function, UtilityClass::format_time. This is the place
where all time formats come together, given the number of seconds and
optionally frames. I have simplified the above-mentioned functions
somewhat, but I haven't given them a complete refactor or renaming -
I mainly made sure that they all use the same backend so I can make the
formats consistent and properly localizable.
(And before we start shoving more temporary char buffers everywhere
just to get rid of the std::string's, maybe we need to think of a
globally used working buffer of size SCREEN_WIDTH_CHARS+1, as a
register of sorts, for when any line of text needs to be made or
processed, then printed, and then goes unused. Maybe help.textrow,
or something like that.)
As for this commit, the available time formats are now more consistent
and changed a little in some places. Leading zeroes for the first unit
are now no longer included, time trial results and the Super Gravitron
can now display hours when they went to 60 minutes before, and we now
always use .CC instead of :CC. These are the formats:
- H:MM:SS
- H:MM:SS.CC
- M:SS
- M:SS.CC
- S.CC (only used when always_minutes=false, for the Gravitrons)
Here's what changes to the current functions:
- Game::partimestring() is removed - it was used in two places, and
could be replaced by game.timetstring(game.timetrialpar)
- Game::giventimestring(h,m,s) and Game::timestring() are now wrappers
for the other functions
- The four remaining functions (Game::resulttimestring(),
Game::timetstring(t), Game::timestringcenti(buffer, buffer_size)
and UtilityClass::timestring(t)) are now wrappers for the "central
function", UtilityClass::format_time.
- UtilityClass::twodigits(int t) is now unused so it's also removed.
- I also added int UtilityClass::hms_to_seconds(int h, int m, int s)
2021-12-25 17:13:46 +01:00
|
|
|
int hms_to_seconds(int h, int m, int s);
|
|
|
|
|
|
|
|
void format_time(char* buffer, const size_t buffer_size, int seconds, int frames, bool always_minutes);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
std::string timestring(int t);
|
|
|
|
|
Add a system for selecting between wordy/wordy2
Some languages have different spellings of wordy numbers based on the
gender of the things they're counting (uno crewmate versus una trinket)
or what a number's role is in the sentence (e.g. twenta out of twentu).
We've always had the idea we couldn't support such complex differences
though, because the game can't be adapted to know what gender each
object will have and what word classes might exist in other languages,
so translators would in those cases just have to forgo the wordy
numbers and just let the game use "20 out of 20".
A solution we came up semi-recently though (after all translations were
finished except for Arabic), was to allow the translator to define
however many classes of wordy numbers they need, and fill them all out.
This would not need the game to be *adapted* for every language's
specific grammar and word genders/classes. Instead, the translator
would just choose their correct self-defined class at the time they use
`wordy` in the VFormat placeholder. Something like
{n|wordy|class=feminine}, or {n|wordy_feminine}.
So this would benefit several languages, but we came up with the
solution a little late for all languages to benefit from it. The Arabic
translators asked for two separate classes of wordy numbers though, so
my plan is to first just have a second list of wordy numbers
(translation2 in numbers.xml), which can be accessed by passing the
`wordy2` flag to VFormat, instead of `wordy`.
Once 2.4 is released, we can take our time to do it properly. This
would involve the ability for translators to define however many
classes they need, to name them what they want, and this name would
then be useable in VFormat placeholders. We can convert all existing
translations to have one class defined by default, such as "wordy", or
"translation" depending on implementation, but there's not so much
concern for maintaining backwards compatibility here, so we can do a
mass-switchover for all language files. That said, it wouldn't be too
hard to add a special case for "translation" being "wordy" either.
We can then ask translators if they would like to change anything with
the new system in place.
For now, we can use this system for Arabic, maybe Spanish since there
were complaints about uno/una, and *maybe* Dutch (it has a thing where
the number "one" is often capitalized differently, but it's not
mandatory per se)
2024-01-06 04:15:06 +01:00
|
|
|
std::string number_words(int _t, const char* number_class);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
static bool intersects( SDL_Rect A, SDL_Rect B );
|
|
|
|
|
Explicitly declare void for all void parameter functions (#628)
Apparently in C, if you have `void test();`, it's completely okay to do
`test(2);`. The function will take in the argument, but just discard it
and throw it away. It's like a trash can, and a rude one at that. If you
declare it like `void test(void);`, this is prevented.
This is not a problem in C++ - doing `void test();` and `test(2);` is
guaranteed to result in a compile error (this also means that right now,
at least in all `.cpp` files, nobody is ever calling a void parameter
function with arguments and having their arguments be thrown away).
However, we may not be using C++ in the future, so I just want to lay
down the precedent that if a function takes in no arguments, you must
explicitly declare it as such.
I would've added `-Wstrict-prototypes`, but it produces an annoying
warning message saying it doesn't work in C++ mode if you're compiling
in C++ mode. So it can be added later.
2021-02-25 23:23:59 +01:00
|
|
|
void updateglow(void);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
int glow;
|
|
|
|
int slowsine;
|
|
|
|
int glowdir;
|
|
|
|
};
|
|
|
|
|
2020-09-28 04:15:06 +02:00
|
|
|
#ifndef HELP_DEFINITION
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
extern UtilityClass help;
|
2020-09-28 04:15:06 +02:00
|
|
|
#endif
|
Allow using help/graphics/music/game/key/map/obj everywhere
This commit makes `help`, `graphics`, `music`, `game`, `key`, `map`, and
`obj` essentially static global objects that can be used everywhere.
This is useful in case we ever need to add a new function in the future,
so we don't have to bother with passing a new argument in which means we
have to pass a new argument in to the function that calls that function
which means having to pass a new argument into the function that calls
THAT function, etc. which is a real headache when working on fan mods of
the source code.
Note that this changes NONE of the existing function signatures, it
merely just makes those variables accessible everywhere in the same way
`script` and `ed` are.
Also note that some classes had to be initialized after the filesystem
was initialized, but C++ would keep initializing them before the
filesystem got initialized, because I *had* to put them at the top of
`main.cpp`, or else they wouldn't be global variables.
The only way to work around this was to use entityclass's initialization
style (which I'm pretty sure entityclass of all things doesn't need to
be initialized this way), where you actually initialize the class in an
`init()` function, and so then you do `graphics.init()` after the
filesystem initialization, AFTER doing `Graphics graphics` up at the
top.
I've had to do this for `graphics` (but only because its child
GraphicsResources `grphx` needs to be initialized this way), `music`,
and `game`. I don't think this will affect anything. Other than that,
`help`, `key`, and `map` are still using the C++-intended method of
having ClassName::ClassName() functions.
2020-01-29 08:35:03 +01:00
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
#endif /* UTILITYCLASS_H */
|