Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add extension to write to cba_settings.sqf file #1333

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

commy2
Copy link
Contributor

@commy2 commy2 commented May 8, 2020

When merged this pull request will:

  • title

It would be super convenient if settings could be written by the game to the settings files directly. So I wanted to have an extension for that. Pardon my poor C++, I have no idea what's going on, lol

READ -> reads setting value & force lvl
WRITE -> overwrites setting with given value & force lvl
CLEAR -> empties cba_settings.sqf
PARSE -> gives a parseSimpleArray'able super array of all settings, value, force lvl triplets. Since extension callback is limited by like 17k chars, this comes in batches of 10k. The idea is to append the 10k batches in SQF before using PSA.

Currently only works for the main game userconfig, not for missions (need to figure out the system path).

while (file.good()) {
getline(file, line);
if (file.good() || line.length() != 0) // Don't explode file by appending duplicate additional newlines.
result = result + trim(line) + "\n";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result = result + trim(line) + "\n";
result.append(trim(line) + "\n");

}

string load(int strlen) {
string result = "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reserve.

result.reserve(std::filesystem::size(SETTINGS_FILE));

Otherwise you resize/reallocate/copy almost every time you append a line.

Copy link
Contributor Author

@commy2 commy2 May 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, didn't know filestream::size is a thing. Though, is it reasonable to allocate ~10k characters (bytes?) at once?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well more reasonable than first allocating 16, then deleting and reallocating 32, then deleting and reallocating 64, then deleting and reallocating 128, then deleting and reallocating 512, then deleting and reallocating 2048, then deleting and reallocating 8192, then...

return "true";
}

string load(int strlen) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strlen is the name of a function, please don't use that as variable name.

Copy link
Contributor Author

@commy2 commy2 May 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha. Oops.

line = trim_left(line);

// Ignore commented out lines.
if (line.substr(0, 2) == "//")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies the string every time. Would be WAY more efficient to use a string_view here, as that is all that you need. For trim_left too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why trim left and right seperately? why not immediately trim both sides?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not immediately trim both sides?

    force force   setting    =    123;

is something one could write. If I process this, I need multiple trims, but not on both sides all the time.

return str.substr(0, last + 1);
}

string trim(string str) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example for a more efficient trim that doesn't copy the string
https://github.com/dedmen/armake/blob/cpp/src/utils.cpp#L240

if (argsCnt >= 3)
force = min(stoi(args[2]), 2);

if (_stricmp(function, "version") == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why even check the whole string?
c
l
p
r
a

the first character would be sufficient for what you need

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True I guess, but what does it matter? This is not something that needs to be particularly fast.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't really matter, just my "every nanosecond counts" bullshit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand.

string result = "false";

ofstream temp;
temp.open(string(TEMP_FILE).c_str(), ios::out);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are reloading, reparsing, rewriting the whole file for every setting property write?
You could just parse it once, and store it in memory. Your extension won't get unloaded

Copy link
Contributor Author

@commy2 commy2 May 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

store it in memory

How?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment below, just a global variable. Array or map or some container


// Count force tags.
int force = 0;
while (line.substr(0, 6) == "force ") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are copying the substring on every iteration here. If you use the string_view trim, it will only move a pointer around

@dedmen
Copy link
Contributor

dedmen commented May 8, 2020

I would

struct settingEntry {
uint8_t force;
std::string settingValue;
}

std::unordered_map<std::string, settingEntry> settings;

on parse then clear map and parse entries into it.
on update you just update the entry, and write out all others. No need to reparse file.

write(setValue) is settings[...] = ... + write
append is settings[...] = ... + write
clear is settings.clear() + write
read is return settings[...]
then you have one parse function that parses file and fills the map
and one function that writes the file from map contents that is called by write/append/clear

also then no need for append vs write, as duplicate entries don't make sense anyway?

Since extension callback is limited by like 17k chars,

is it? I thought callbacks were unlimited

@commy2
Copy link
Contributor Author

commy2 commented May 8, 2020

is it? I thought callbacks were unlimited

It only gave me that long a string (17 something k characters) when I tried how large a settings file I can get.

@commy2
Copy link
Contributor Author

commy2 commented May 8, 2020

I would
unordered_map

But what if I edit the file while the game is running?

@dedmen
Copy link
Contributor

dedmen commented May 8, 2020

But what if I edit the file while the game is running?

store file change date of last parse/write.
Then when reading check file change date, if it was edited you need to reload. If not then not.

Copy link

@TheMagnetar TheMagnetar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some comments.

strncpy_s(output, outputSize, result.c_str(), _TRUNCATE);\
return (val)

#define SYNTAX_ERROR_WRONG_PARAMS_SIZE 101
Copy link

@TheMagnetar TheMagnetar May 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since dedmen is suggesting c++17 features (string_view, filesystem) it is also advisable to use for numerical data the following:

Suggested change
#define SYNTAX_ERROR_WRONG_PARAMS_SIZE 101
static constexpr int32_t SYNTAX_ERROR_WRONG_PARAMS_SIZE = 101;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit over

const int SYNTAX_ERROR_WRONG_PARAMS_SIZE = 101;

?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constexpr is evaluated at compile time

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which won't make any difference here as the end result.

#define DELIM ":"
#define MAX_CALLBACK_LENGTH 10000

using namespace std;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not recommended in general and it is rather dangerous.


file.close();

if (strlen * MAX_CALLBACK_LENGTH > result.length())
Copy link

@TheMagnetar TheMagnetar May 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not writing { } after if/while etc is not recommended at all. Consider the following

if (strlen * MAX_CALLBACK_LENGTH > result.length())
    do_stuf()
do_other_stuff

and then one decides to comment do_stuff

if (strlen * MAX_CALLBACK_LENGTH > result.length())
    // do_stuf()
do_other_stuff

resulting in do_other_stuff being inside the if context breaking a lot of stuff

return result;
}

string append(string setting, string value, int force) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing by reference is advisable with classes. It avoids copying the whole class. The same can be applied to the other functions.

Suggested change
string append(string setting, string value, int force) {
string append(const string &setting, const string &value, int force) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Didn't know it copies, and that this is how one prevents it.


string line = "";
while (force-- > 0)
line = line + "force ";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
line = line + "force ";
line.append("force ");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants