Files
starruler-linux/source/game/scripts/bind_formula.cpp
T
2018-07-17 14:15:37 +02:00

528 lines
13 KiB
C++

#include "binds.h"
#include "util/formula.h"
#include "util/refcount.h"
#include "util/save_file.h"
#include "network/message.h"
#include "main/logging.h"
#include "scripts/context_cache.h"
#include "general_states.h"
#include <unordered_map>
#include <vector>
namespace scripts {
class FormulaNamespace;
static int varIndex(const std::string* str);
static double varName(void* user, const std::string* name);
static double nsVar(void* user, int index);
static threads::ReadWriteMutex nsVarMutex;
static std::unordered_map<std::string, int> nsVariables;
static std::vector<std::string> nsVariableNames;
class FormulaNamespace : public AtomicRefCounted {
public:
enum VarType {
vConstant,
vFormula,
};
struct Var {
VarType type;
union {
double decimal;
Formula* formula;
};
std::string* str;
Var() : type(vConstant), decimal(0.0), str(0) {
}
~Var() {
switch(type) {
case vFormula:
delete formula;
break;
}
delete str;
}
void setType(VarType newType) {
switch(type) {
case vFormula:
delete formula;
break;
}
type = newType;
}
void setString(const std::string& value) {
if(!str)
str = new std::string();
*str = value;
}
void write(net::Message& msg) {
msg << (uint8_t)type;
switch(type) {
case vConstant:
msg << decimal;
break;
case vFormula:
if(str)
msg << *str;
else
msg << "0";
break;
}
}
void read(net::Message& msg) {
auto prevType = type;
uint8_t utp;
msg >> utp;
type = (VarType)utp;
switch(type) {
case vConstant:
msg >> decimal;
break;
case vFormula:
if(!str)
str = new std::string();
msg >> *str;
if(prevType == vFormula && formula)
delete formula;
formula = Formula::fromInfix(str->c_str(), &varIndex);
break;
}
}
};
threads::ReadWriteMutex mtx;
std::vector<Var> variables;
std::vector<int> indexes;
int lookup(const std::string& name, bool create = true) {
{
threads::ReadLock nl(nsVarMutex);
auto it = nsVariables.find(name);
if(it != nsVariables.end())
return it->second;
}
if(create) {
int globInd = -1;
{
threads::WriteLock wl(nsVarMutex);
globInd = nsVariables.size();
nsVariables[name] = globInd;
nsVariableNames.push_back(name);
}
{
threads::WriteLock wl(mtx);
int index = variables.size();
variables.push_back(Var());
if((unsigned)globInd >= indexes.size())
indexes.resize(globInd+1, -1);
indexes[globInd] = index;
}
return globInd;
}
else {
return -1;
}
}
inline int fromGlobalIndex(int index) {
if((unsigned)index >= indexes.size())
return -1;
return indexes[index];
}
double get(const std::string& name) {
threads::ReadLock rl(mtx);
int index = lookup(name, false);
if(index == -1) {
error("Formula variable '%s' does not exist.", name.c_str());
scripts::logException();
return 0.0;
}
return get(index);
}
double get(int globalIndex) {
threads::ReadLock rl(mtx);
int index = fromGlobalIndex(globalIndex);
if(index == -1) {
if(globalIndex > 0 && globalIndex < (int)nsVariableNames.size()) {
//Formula variable exists, just hasn't been set in this particular namespace,
//which means we should consider it a 0 so we don't fuck with execution order
//error("Formula variable '%s' does not exist.", nsVariableNames[globalIndex].c_str());
//scripts::logException();
return 0.0;
}
else {
error("Invalid namespace variable index.");
scripts::logException();
}
return 0.0;
}
Var& v = variables[index];
switch(v.type) {
case vConstant:
return v.decimal;
case vFormula:
return v.formula->evaluate(&varName, this, &nsVar);
}
return 0.0;
}
bool has(const std::string& name) {
threads::ReadLock rl(mtx);
return fromGlobalIndex(lookup(name, false)) != -1;
}
void setConstant(const std::string& name, double value) {
threads::WriteLock wl(mtx);
int index = lookup(name, true);
setConstant(index, value);
}
void setConstant(int globIndex, double value) {
threads::WriteLock wl(mtx);
int index = fromGlobalIndex(globIndex);
if(index == -1) {
index = variables.size();
variables.push_back(Var());
if((unsigned)globIndex >= indexes.size())
indexes.resize(globIndex+1, -1);
indexes[globIndex] = index;
}
Var& v = variables[index];
v.setType(vConstant);
v.decimal = value;
}
void modConstant(int globIndex, double value) {
threads::WriteLock wl(mtx);
int index = fromGlobalIndex(globIndex);
if(index == -1) {
index = variables.size();
variables.push_back(Var());
if((unsigned)globIndex >= indexes.size())
indexes.resize(globIndex+1, -1);
indexes[globIndex] = index;
}
Var& v = variables[index];
if(v.type == vConstant) {
v.decimal = v.decimal + value;
}
else {
double last = get(globIndex);
v.setType(vConstant);
v.decimal = last + value;
}
}
void setFormula(const std::string& name, const std::string& formula) {
threads::ReadLock rl(mtx);
int index = lookup(name, true);
setFormula(index, formula);
}
void setFormula(int globIndex, const std::string& formula) {
threads::WriteLock wl(mtx);
int index = fromGlobalIndex(globIndex);
if(index == -1) {
index = variables.size();
variables.push_back(Var());
if((unsigned)globIndex >= indexes.size())
indexes.resize(globIndex+1, -1);
indexes[globIndex] = index;
}
Var& v = variables[index];
v.setType(vFormula);
v.formula = Formula::fromInfix(formula.c_str(), &varIndex);
v.setString(formula);
}
void write(net::Message& msg) {
threads::ReadLock rl(mtx);
msg << (unsigned)variables.size();
for(unsigned i = 0, cnt = indexes.size(); i < cnt; ++i) {
if(indexes[i] == -1)
continue;
msg << nsVariableNames[i];
variables[indexes[i]].write(msg);
}
}
void read(net::Message& msg) {
try {
threads::WriteLock wl(mtx);
unsigned varCnt = 0;
msg >> varCnt;
for(unsigned i = 0; i < varCnt; ++i) {
std::string name;
msg >> name;
int globIndex = lookup(name, true);
int index = fromGlobalIndex(globIndex);
if(index == -1) {
index = variables.size();
variables.push_back(Var());
if((unsigned)globIndex >= indexes.size())
indexes.resize(globIndex+1, -1);
indexes[globIndex] = index;
}
variables[index].read(msg);
}
}
catch(net::MessageReadError) {
scripts::throwException("Error reading from message: end of message.");
}
}
};
static FormulaNamespace* makeNamespace() {
return new FormulaNamespace();
}
static int varIndex(const std::string* name) {
{
threads::ReadLock nl(nsVarMutex);
auto it = nsVariables.find(*name);
if(it != nsVariables.end())
return it->second;
}
threads::WriteLock wl(nsVarMutex);
auto it = nsVariables.find(*name);
if(it != nsVariables.end())
return it->second;
int globInd = nsVariables.size();
nsVariables[*name] = globInd;
nsVariableNames.push_back(*name);
return globInd;
}
static double varName(void* user, const std::string* name) {
return 0.0;
}
static double nsVar(void* user, int index) {
FormulaNamespace* ns = (FormulaNamespace*)user;
if(ns)
return ns->get(index);
else
return 0.0;
}
class ScriptFormula : public AtomicRefCounted {
public:
Formula* formula;
ScriptFormula() : formula(0) {
}
void parse(const std::string& expr) {
if(formula)
delete formula;
try {
formula = Formula::fromInfix(expr.c_str(), &varIndex);
}
catch(FormulaError& err) {
error("Script Formula Error: %s", err.msg.c_str());
scripts::logException();
formula = 0;
}
}
double evaluate(FormulaNamespace* ns = 0) {
if(!formula)
return 0.0;
return formula->evaluate(&varName, ns, &nsVar);
}
~ScriptFormula() {
delete formula;
}
};
static ScriptFormula* makeFormula_e() {
return new ScriptFormula();
}
static ScriptFormula* makeFormula(const std::string& expr) {
ScriptFormula* f = new ScriptFormula();
f->parse(expr);
return f;
}
void RegisterFormulaBinds(bool server) {
nsVariables.clear();
nsVariableNames.clear();
/* FORMULA NAMESPACE */
ClassBind ns("Namespace", asOBJ_REF);
classdoc(ns, "A namespace of variables that can be accessed from formulas attached to it.");
ns.addFactory("Namespace@ f()", asFUNCTION(makeNamespace));
ns.setReferenceFuncs(asMETHOD(FormulaNamespace,grab), asMETHOD(FormulaNamespace,drop));
ns.addMember("ReadWriteMutex mtx", offsetof(FormulaNamespace, mtx))
doc("Mutex that governs reading and writing on this namespace.");
ns.addMethod("int lookup(const string&in name, bool create = true)",
asMETHOD(FormulaNamespace, lookup))
doc("Lookup the index of a variable in the namespace.",
"Name of the variable.",
"If true, create the variable if it does not exist.",
"Index of the variable. -1 if it does not exist and was not created.");
ns.addMethod("double get(const string&in name)",
asMETHODPR(FormulaNamespace, get, (const std::string&), double))
doc("Get or calculate the value of a variable by name.",
"Name of the variable.",
"Value of that variable.");
ns.addMethod("double get(int index)",
asMETHODPR(FormulaNamespace, get, (int), double))
doc("Get or calculate the value of a variable by index.",
"Index of the variable.",
"Value of that variable.");
ns.addMethod("bool has(const string&in name)",
asMETHOD(FormulaNamespace, has))
doc("", "Name of the variable to check for.",
"True if a variable with this name exists.");
ns.addMethod("void setConstant(const string&in name, double value)",
asMETHODPR(FormulaNamespace, setConstant, (const std::string&, double), void))
doc("Set a constant value for a variable.",
"Name of the variable to set.",
"Value to set the variable to.");
ns.addMethod("void setConstant(int index, double value)",
asMETHODPR(FormulaNamespace, setConstant, (int, double), void))
doc("Set a constant value for a variable.",
"Index of the variable to set.",
"Value to set the variable to.");
ns.addMethod("void modConstant(int index, double value)",
asMETHODPR(FormulaNamespace, modConstant, (int, double), void))
doc("Modify a constant value for a variable by adding a new value.",
"Index of the variable to set.",
"Amount to add to the constant value.");
ns.addMethod("void setFormula(const string&in name, const string&in formula)",
asMETHODPR(FormulaNamespace, setFormula, (const std::string&, const std::string&), void))
doc("Set a formula to evaluate for a variable. Formula is evaluated in this namespace.",
"Name of the variable to set.",
"Formula to set the variable to.");
ns.addMethod("void setFormula(int index, const string&in formula)",
asMETHODPR(FormulaNamespace, setFormula, (int, const std::string&), void))
doc("Set a formula to evaluate for a variable. Formula is evaluated in this namespace.",
"Index of the variable to set.",
"Formula to set the variable to.");
ns.addMethod("void write(Message& msg)", asMETHOD(FormulaNamespace, write))
doc("Write the namespace to a message.", "Message to write to.");
ns.addMethod("void read(Message& msg)", asMETHOD(FormulaNamespace, read))
doc("Read the namespace from a message.", "Message to read from.");
ns.addMethod("void save(SaveFile& file)", asMETHOD(FormulaNamespace, write))
doc("Write the namespace to a save file.", "Save file to write to.");
ns.addMethod("void load(SaveFile& file)", asMETHOD(FormulaNamespace, read))
doc("Read the namespace from a save file.", "Save file to read from.");
{
Namespace ns("formula");
bind("int variable(const ::string&in name)", asFUNCTION(varIndex));
}
/* FORMULA */
ClassBind f("Formula", asOBJ_REF);
classdoc(f, "Evaluator for arbitrary formula expressions.");
f.addFactory("Formula@ f()", asFUNCTION(makeFormula_e));
f.addFactory("Formula@ f(const string&in formula)", asFUNCTION(makeFormula))
doc("Construct a new formula.", "Formula expression to use.", "Constructed formula.");
f.setReferenceFuncs(asMETHOD(ScriptFormula,grab), asMETHOD(ScriptFormula,drop));
f.addMethod("void parse(const string&in formula)", asMETHOD(ScriptFormula, parse))
doc("Parse an expression into this formula.", "Formula expression to use.");
f.addMethod("double evaluate(Namespace@ ns = null)", asMETHOD(ScriptFormula, evaluate))
doc("Evaluate the formula expression with the current state of "
"the namespace it was constructed with.",
"Namespace to retrieve variables from.",
"Value of the formula.");
}
void addNamespaceState() {
stateValueTypes["Namespace"].setup(
sizeof(FormulaNamespace*), "Namespace@",
//Copy reference only, or create new if not copying
[](void* m, void* s) {
FormulaNamespace** dest = (FormulaNamespace**)m;
FormulaNamespace** src = (FormulaNamespace**)s;
if(src) {
*dest = *src;
if(*src)
(*src)->grab();
}
else {
*dest = new FormulaNamespace();
}
},
//No initializer
nullptr,
//Release reference on destruct
[](void* mem) {
FormulaNamespace* ns = *(FormulaNamespace**)mem;
if(ns)
ns->drop();
},
//Write to network
[](net::Message& msg, void* mem) {
FormulaNamespace* ns = *(FormulaNamespace**)mem;
if(ns)
ns->write(msg);
},
//Read from network
[](net::Message& msg, void* mem) {
FormulaNamespace* ns = *(FormulaNamespace**)mem;
if(ns)
ns->read(msg);
}
);
}
};