Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
enum BasicTypes {
|
||||
BT_Int,
|
||||
BT_Double,
|
||||
BT_Bool
|
||||
};
|
||||
|
||||
struct BasicType {
|
||||
BasicTypes type;
|
||||
union {
|
||||
int integer;
|
||||
double decimal;
|
||||
bool boolean;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "bbcode.h"
|
||||
|
||||
enum ParseState {
|
||||
PS_Text,
|
||||
PS_StartTag,
|
||||
PS_EndTag
|
||||
};
|
||||
|
||||
static const char* parseTag(BBCode::Tag& root, const char* str) {
|
||||
bool escaped = false;
|
||||
bool selfClose = false;
|
||||
ParseState state = PS_Text;
|
||||
const char* start = str;
|
||||
unsigned tagDepth = 0;
|
||||
|
||||
while(*str != 0) {
|
||||
unsigned char c = *str;
|
||||
switch(state) {
|
||||
case PS_Text:
|
||||
if(c == '\\') {
|
||||
if(escaped) {
|
||||
escaped = false;
|
||||
if(str != start) {
|
||||
BBCode::Tag t;
|
||||
t.type = -1;
|
||||
t.argument = std::string(start, str - start);
|
||||
root.contents.push_back(t);
|
||||
}
|
||||
++str;
|
||||
start = str;
|
||||
}
|
||||
else {
|
||||
escaped = true;
|
||||
++str;
|
||||
}
|
||||
}
|
||||
else if(c == '[') {
|
||||
if(escaped) {
|
||||
--str;
|
||||
if(str != start) {
|
||||
BBCode::Tag t;
|
||||
t.type = -1;
|
||||
t.argument = std::string(start, str - start);
|
||||
root.contents.push_back(t);
|
||||
}
|
||||
++str;
|
||||
|
||||
escaped = false;
|
||||
start = str;
|
||||
++str;
|
||||
}
|
||||
else {
|
||||
if(str != start) {
|
||||
BBCode::Tag t;
|
||||
t.type = -1;
|
||||
t.argument = std::string(start, str - start);
|
||||
root.contents.push_back(t);
|
||||
}
|
||||
|
||||
state = PS_StartTag;
|
||||
++str;
|
||||
start = str;
|
||||
}
|
||||
}
|
||||
else {
|
||||
++str;
|
||||
}
|
||||
break;
|
||||
case PS_StartTag:
|
||||
if(c == '/') {
|
||||
if(str == start) {
|
||||
state = PS_EndTag;
|
||||
++str;
|
||||
start = str;
|
||||
}
|
||||
else {
|
||||
selfClose = true;
|
||||
++str;
|
||||
}
|
||||
}
|
||||
else if(c == ']') {
|
||||
if(tagDepth > 0) {
|
||||
--tagDepth;
|
||||
++str;
|
||||
selfClose = true;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string tagname;
|
||||
if(selfClose)
|
||||
tagname = std::string(start, (str - 1) - start);
|
||||
else
|
||||
tagname = std::string(start, str - start);
|
||||
++str;
|
||||
|
||||
BBCode::Tag t;
|
||||
auto argpos = tagname.find('=');
|
||||
if(argpos != std::string::npos) {
|
||||
t.name = tagname.substr(0, argpos);
|
||||
if(argpos < tagname.size() - 1)
|
||||
t.argument = tagname.substr(argpos+1);
|
||||
}
|
||||
else {
|
||||
t.name = tagname;
|
||||
}
|
||||
|
||||
if(selfClose)
|
||||
selfClose = false;
|
||||
else
|
||||
str = parseTag(t, str);
|
||||
state = PS_Text;
|
||||
start = str;
|
||||
|
||||
root.contents.push_back(t);
|
||||
}
|
||||
else {
|
||||
if(c == '[')
|
||||
++tagDepth;
|
||||
selfClose = false;
|
||||
++str;
|
||||
}
|
||||
break;
|
||||
case PS_EndTag:
|
||||
if(c == ']') {
|
||||
std::string tagname(start, str - start);
|
||||
++str;
|
||||
|
||||
if(tagname != root.name)
|
||||
throw "Unexpected close tag.";
|
||||
return str;
|
||||
}
|
||||
else {
|
||||
++str;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
++str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(state == PS_Text && str != start) {
|
||||
BBCode::Tag t;
|
||||
t.type = -1;
|
||||
t.argument = std::string(start, str - start);
|
||||
root.contents.push_back(t);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void BBCode::parse(const std::string& content) {
|
||||
//Clear everything
|
||||
root.contents.clear();
|
||||
|
||||
//Start parsing
|
||||
parseTag(root, content.c_str());
|
||||
}
|
||||
|
||||
void BBCode::clear() {
|
||||
root.contents.clear();
|
||||
}
|
||||
|
||||
BBCode::BBCode() {
|
||||
}
|
||||
|
||||
BBCode::~BBCode() {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class BBCode {
|
||||
public:
|
||||
struct Tag {
|
||||
int type;
|
||||
int value;
|
||||
std::string name;
|
||||
std::string argument;
|
||||
|
||||
std::vector<Tag> contents;
|
||||
|
||||
Tag() : type(0), value(0) {
|
||||
}
|
||||
};
|
||||
|
||||
Tag root;
|
||||
void parse(const std::string& content);
|
||||
void clear();
|
||||
|
||||
BBCode();
|
||||
~BBCode();
|
||||
};
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "util/elevation_map.h"
|
||||
#include "main/references.h"
|
||||
#include "compat/misc.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "BiPatch/bilinear.h"
|
||||
#include "main/logging.h"
|
||||
|
||||
ElevationMap::ElevationMap()
|
||||
: generated(false), grid(0) {
|
||||
}
|
||||
|
||||
ElevationMap::~ElevationMap() {
|
||||
if(generated)
|
||||
free(grid);
|
||||
}
|
||||
|
||||
void ElevationMap::clear() {
|
||||
points.clear();
|
||||
if(generated) {
|
||||
free(grid);
|
||||
grid = 0;
|
||||
generated = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ElevationMap::addPoint(const vec3d& point, double radius) {
|
||||
Point p = {point, radius};
|
||||
points.push_back(p);
|
||||
}
|
||||
|
||||
void ElevationMap::generate(const vec2d& interval, double power) {
|
||||
//Clear previous grid
|
||||
if(generated) {
|
||||
free(grid);
|
||||
grid = 0;
|
||||
}
|
||||
|
||||
//A grid with no points is pretty pointless (haha)
|
||||
if(points.empty()) {
|
||||
generated = true;
|
||||
grid = 0;
|
||||
gridStart = vec3d();
|
||||
gridSize = vec2d();
|
||||
gridInterval = vec2d();
|
||||
return;
|
||||
}
|
||||
|
||||
double start = devices.driver->getAccurateTime();
|
||||
|
||||
//Get the extents of the grid
|
||||
vec2d topLeft(points[0].center.x, points[0].center.z);
|
||||
vec2d botRight = topLeft;
|
||||
double avgHeight = 0.0;
|
||||
|
||||
foreach(p, points) {
|
||||
if(p->center.x - p->radius < topLeft.x)
|
||||
topLeft.x = p->center.x - p->radius;
|
||||
if(p->center.x + p->radius > botRight.x)
|
||||
botRight.x = p->center.x + p->radius;
|
||||
if(p->center.z - p->radius < topLeft.y)
|
||||
topLeft.y = p->center.z - p->radius;
|
||||
if(p->center.z + p->radius > botRight.y)
|
||||
botRight.y = p->center.z + p->radius;
|
||||
avgHeight += p->center.y;
|
||||
}
|
||||
|
||||
avgHeight /= points.size();
|
||||
gridSize = botRight - topLeft;
|
||||
gridStart = vec3d(topLeft.x, avgHeight, topLeft.y);
|
||||
gridInterval = interval;
|
||||
gridResolution.x = (int)ceil(gridSize.x / gridInterval.x);
|
||||
gridResolution.y = (int)ceil(gridSize.y / gridInterval.y);
|
||||
minHeight = HUGE_VAL;
|
||||
maxHeight = -HUGE_VAL;
|
||||
|
||||
//Create the big grid for optimization
|
||||
std::vector<std::vector<Point>> bigGrid;
|
||||
unsigned bigGridSize = std::max((unsigned)sqrt((double)points.size()), 1u);
|
||||
bigGrid.resize(bigGridSize * bigGridSize);
|
||||
double bigInterval = gridSize.x / bigGridSize;
|
||||
|
||||
foreach(p, points) {
|
||||
unsigned bigX = std::min((unsigned)((p->center.x - topLeft.x) / bigInterval), bigGridSize - 1);
|
||||
unsigned bigY = std::min((unsigned)((p->center.z - topLeft.y) / bigInterval), bigGridSize - 1);
|
||||
|
||||
bigGrid[bigX + (bigY * bigGridSize)].push_back(*p);
|
||||
}
|
||||
|
||||
//Interpolate within the grid
|
||||
grid = (float*)calloc(gridResolution.x * gridResolution.y, sizeof(float));
|
||||
generated = true;
|
||||
|
||||
vec2d realPos;
|
||||
double height;
|
||||
double totalWeight = 0.0;
|
||||
|
||||
auto calcBucket = [&](unsigned bigX, unsigned bigY) {
|
||||
if(bigX >= bigGridSize || bigY >= bigGridSize)
|
||||
return;
|
||||
|
||||
auto& bucket = bigGrid[bigX + (bigY * bigGridSize)];
|
||||
foreach(it, bucket) {
|
||||
//Check if the point is inside a system
|
||||
vec2d flatPos(it->center.x, it->center.z);
|
||||
double dist = realPos.distanceTo(flatPos);
|
||||
|
||||
if(dist < it->radius) {
|
||||
height = it->center.y;
|
||||
totalWeight = 1.0;
|
||||
break;
|
||||
}
|
||||
|
||||
//Do inverse distance weighting
|
||||
double w = 1.0 / pow(dist - it->radius, power);
|
||||
height += it->center.y * w;
|
||||
totalWeight += w;
|
||||
}
|
||||
};
|
||||
|
||||
for(int iy = 0; iy < gridResolution.y; ++iy) {
|
||||
for(int ix = 0; ix < gridResolution.x; ++ix) {
|
||||
realPos = vec2d(gridStart.x + ix * gridInterval.x,
|
||||
gridStart.z + iy * gridInterval.y);
|
||||
|
||||
height = 0.0;
|
||||
totalWeight = 0.0;
|
||||
|
||||
unsigned bigX = std::min((unsigned)((realPos.x - gridStart.x) / bigInterval), bigGridSize - 1);
|
||||
unsigned bigY = std::min((unsigned)((realPos.y - gridStart.z) / bigInterval), bigGridSize - 1);
|
||||
|
||||
calcBucket(bigX - 1, bigY - 1);
|
||||
calcBucket(bigX, bigY - 1);
|
||||
calcBucket(bigX + 1, bigY - 1);
|
||||
|
||||
calcBucket(bigX - 1, bigY);
|
||||
calcBucket(bigX, bigY);
|
||||
calcBucket(bigX + 1, bigY);
|
||||
|
||||
calcBucket(bigX - 1, bigY + 1);
|
||||
calcBucket(bigX, bigY + 1);
|
||||
calcBucket(bigX + 1, bigY + 1);
|
||||
|
||||
if(totalWeight == 0) {
|
||||
height = avgHeight;
|
||||
}
|
||||
else {
|
||||
height /= totalWeight;
|
||||
if(height < minHeight)
|
||||
minHeight = height;
|
||||
if(height > maxHeight)
|
||||
maxHeight = height;
|
||||
}
|
||||
|
||||
grid[iy * gridResolution.x + ix] = (float)height;
|
||||
}
|
||||
}
|
||||
|
||||
if(maxHeight == minHeight) {
|
||||
maxHeight += 1.0;
|
||||
minHeight -= 1.0;
|
||||
}
|
||||
|
||||
double time = devices.driver->getAccurateTime() - start;
|
||||
info("Elevation grid took %.3gms to calculate.", time * 1000.0);
|
||||
}
|
||||
|
||||
double ElevationMap::lookup(int x, int y) {
|
||||
if(gridResolution.x == 0 || gridResolution.y == 0)
|
||||
return 0.0;
|
||||
x = std::max(std::min(x, gridResolution.x - 1), 0);
|
||||
y = std::max(std::min(y, gridResolution.y - 1), 0);
|
||||
return (double)grid[y * gridResolution.x + x];
|
||||
}
|
||||
|
||||
double ElevationMap::get(vec2d point) {
|
||||
return get(point.x, point.y);
|
||||
}
|
||||
|
||||
double ElevationMap::get(double x, double y) {
|
||||
//Move coordinates to grid coordinates
|
||||
x = (x - gridStart.x) / gridInterval.x;
|
||||
y = (y - gridStart.z) / gridInterval.y;
|
||||
|
||||
//Get the coordinates of the nearby points
|
||||
int lx = (int)floor(x);
|
||||
int ly = (int)floor(y);
|
||||
int rx = lx + 1;
|
||||
int ry = ly + 1;
|
||||
|
||||
//Interpolate the values
|
||||
double value = 0.0;
|
||||
value += lookup(lx, ly) * ((double)rx - x) * ((double)ry - y);
|
||||
value += lookup(rx, ly) * (x - (double)lx) * ((double)ry - y);
|
||||
value += lookup(lx, ry) * ((double)rx - x) * (y - (double)ly);
|
||||
value += lookup(rx, ry) * (x - (double)lx) * (y - (double)ly);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool ElevationMap::getClosestPoint(const line3dd& inLine, vec3d& closestPoint) {
|
||||
if(!generated)
|
||||
return false;
|
||||
|
||||
//Limit the line to the confines of the 3d grid
|
||||
// Always make sure the end goes to the other plane, or a line that stops before the region will never collide
|
||||
vec3d start = inLine.start, end = inLine.end;
|
||||
if(inLine.start.y > inLine.end.y) {
|
||||
if(start.y > maxHeight)
|
||||
inLine.intersectY(start, maxHeight, false);
|
||||
inLine.intersectY(end, minHeight, false);
|
||||
}
|
||||
else {
|
||||
if(start.y < minHeight)
|
||||
inLine.intersectY(start, minHeight, false);
|
||||
inLine.intersectY(end, maxHeight, false);
|
||||
}
|
||||
line3dd line(start, end);
|
||||
|
||||
vec3d lineDir = line.end - line.start;
|
||||
BiPatch::Vector rayOrigin(line.start.x, line.start.y, line.start.z);
|
||||
BiPatch::Vector rayDir(lineDir.x, lineDir.y, lineDir.z);
|
||||
rayDir.normalize();
|
||||
BiPatch::Vector uv;
|
||||
|
||||
//Flatten the line to intelligently chose grid spaces to test
|
||||
line3dd flatLine(start, end);
|
||||
flatLine.start.y = 0;
|
||||
flatLine.end.y = 0;
|
||||
|
||||
vec3d flatPoint = flatLine.start;
|
||||
vec3d flatDir = flatLine.getDirection();
|
||||
|
||||
int x = (int)floor((flatLine.start.x - gridStart.x) / gridInterval.x);
|
||||
int y = (int)floor((flatLine.start.z - gridStart.z) / gridInterval.y);
|
||||
|
||||
for(unsigned checks = 0; checks < 1000; ++checks) {
|
||||
//See if we have a collision
|
||||
double absx = gridStart.x + (gridInterval.x * double(x));
|
||||
double absy = gridStart.z + (gridInterval.y * double(y));
|
||||
|
||||
BiPatch::Vector tl(absx, lookup(x, y), absy);
|
||||
BiPatch::Vector tr(absx + gridInterval.x, lookup(x+1, y), absy);
|
||||
BiPatch::Vector bl(absx, lookup(x, y+1), absy + gridInterval.y);
|
||||
BiPatch::Vector br(absx + gridInterval.x, lookup(x+1, y+1), absy + gridInterval.y);
|
||||
|
||||
BiPatch::BilinearPatch bp(tl, tr, bl, br);
|
||||
if(bp.RayPatchIntersection(rayOrigin, rayDir, uv)) {
|
||||
BiPatch::Vector point = bp.SrfEval(uv.x(), uv.y());
|
||||
vec3d intersect(point.x(), point.y(), point.z());
|
||||
|
||||
//Once we have a collision, it must be the closest point
|
||||
double dot = (intersect - line.start).dot(line.getDirection());
|
||||
if(dot >= -0.0001) {
|
||||
closestPoint = intersect;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Step to the next grid section
|
||||
if(flatDir.z > 0) {
|
||||
vec3d intersect;
|
||||
flatLine.intersectZ(intersect, absy + gridInterval.y, false);
|
||||
if(intersect.x < absx + gridInterval.x && intersect.x > absx) {
|
||||
y += 1;
|
||||
}
|
||||
else if(flatDir.x > 0) {
|
||||
x += 1;
|
||||
flatLine.intersectX(intersect, absx + gridInterval.x, false);
|
||||
}
|
||||
else {
|
||||
x -= 1;
|
||||
flatLine.intersectX(intersect, absx, false);
|
||||
}
|
||||
flatPoint = intersect;
|
||||
}
|
||||
else {//if(flatDir.z <= 0) {
|
||||
vec3d intersect;
|
||||
flatLine.intersectZ(intersect, absy, false);
|
||||
if(intersect.x < absx + gridInterval.x && intersect.x > absx) {
|
||||
y -= 1;
|
||||
}
|
||||
else if(flatDir.x > 0) {
|
||||
x += 1;
|
||||
flatLine.intersectX(intersect, absx + gridInterval.x, false);
|
||||
}
|
||||
else {
|
||||
x -= 1;
|
||||
flatLine.intersectX(intersect, absx, false);
|
||||
}
|
||||
flatPoint = intersect;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "vec2.h"
|
||||
#include "vec3.h"
|
||||
#include "line3d.h"
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* An elevation map takes an irregularly spaced
|
||||
* set of points, precalculates a regular grid of
|
||||
* smoothly interpolated heights and then allows
|
||||
* for elevation lookup for arbitrary locations.
|
||||
*/
|
||||
|
||||
class ElevationMap {
|
||||
public:
|
||||
struct Point {
|
||||
vec3d center;
|
||||
double radius;
|
||||
};
|
||||
std::vector<Point> points;
|
||||
bool generated;
|
||||
float* grid;
|
||||
|
||||
vec3d gridStart;
|
||||
vec2d gridSize;
|
||||
vec2d gridInterval;
|
||||
vec2i gridResolution;
|
||||
double minHeight;
|
||||
double maxHeight;
|
||||
|
||||
void clear();
|
||||
void addPoint(const vec3d& point, double radius = 0.0);
|
||||
void generate(const vec2d& interval, double power = 2.0);
|
||||
|
||||
double lookup(int x, int y);
|
||||
|
||||
double get(vec2d point);
|
||||
double get(double x, double y);
|
||||
|
||||
bool getClosestPoint(const line3dd& line, vec3d& point);
|
||||
|
||||
ElevationMap();
|
||||
~ElevationMap();
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
#include "format.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
void format(std::string& arg, const char* fmt, unsigned argn, FormatArg* argv) {
|
||||
char buffer[2048];
|
||||
|
||||
unsigned left = 2048;
|
||||
char* start = buffer;
|
||||
|
||||
while(*fmt != '\0' && left != 0) {
|
||||
if(*fmt == '$') {
|
||||
++fmt;
|
||||
if(*fmt == '\0')
|
||||
break;
|
||||
if(*fmt >= '1' && *fmt <= '9') {
|
||||
unsigned arg = *fmt - '1';
|
||||
if(arg < argn) {
|
||||
unsigned printed = argv[arg].print(start, left);
|
||||
start += printed;
|
||||
left -= printed;
|
||||
}
|
||||
else {
|
||||
//Print nothing (safe error)
|
||||
}
|
||||
++fmt;
|
||||
}
|
||||
else {
|
||||
*start = '$';
|
||||
++start; --left;
|
||||
if(left != 0) {
|
||||
*start = *fmt;
|
||||
++start;
|
||||
--left;
|
||||
++fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
*start = *fmt;
|
||||
++start; ++fmt; --left;
|
||||
}
|
||||
}
|
||||
|
||||
arg.assign(buffer, 2048 - left);
|
||||
};
|
||||
|
||||
std::string format(const char* fmt, FormatArg a1) {
|
||||
std::string output;
|
||||
format(output, fmt, 1, &a1);
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, FormatArg a1, FormatArg a2) {
|
||||
std::string output;
|
||||
FormatArg args[2] = { a1, a2 };
|
||||
format(output, fmt, 2, args);
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, FormatArg a1, FormatArg a2, FormatArg a3) {
|
||||
std::string output;
|
||||
FormatArg args[3] = { a1, a2, a3 };
|
||||
format(output, fmt, 3, args);
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, FormatArg a1, FormatArg a2, FormatArg a3, FormatArg a4) {
|
||||
std::string output;
|
||||
FormatArg args[4] = { a1, a2, a3, a4 };
|
||||
format(output, fmt, 4, args);
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string format(const char* fmt, FormatArg a1, FormatArg a2, FormatArg a3, FormatArg a4, FormatArg a5) {
|
||||
std::string output;
|
||||
FormatArg args[5] = { a1, a2, a3, a4, a5 };
|
||||
format(output, fmt, 5, args);
|
||||
return output;
|
||||
}
|
||||
|
||||
FormatArg::FormatArg() : type(Arg_int), i(0) {}
|
||||
FormatArg::FormatArg(float F) : type(Arg_float), f(F) {}
|
||||
FormatArg::FormatArg(double D) : type(Arg_double), d(D) {}
|
||||
FormatArg::FormatArg(unsigned U) : type(Arg_unsigned), u(U) {}
|
||||
FormatArg::FormatArg(int I) : type(Arg_int), i(I) {}
|
||||
FormatArg::FormatArg(const char* C) : type(Arg_cstr), c(C) {}
|
||||
FormatArg::FormatArg(const std::string& S) : type(Arg_string), s(&S) {}
|
||||
|
||||
unsigned FormatArg::print(char* buffer, unsigned space) {
|
||||
int printed = 0;
|
||||
switch(type) {
|
||||
case Arg_float:
|
||||
printed = snprintf(buffer, space, "%f", f);
|
||||
break;
|
||||
case Arg_double:
|
||||
printed = snprintf(buffer, space, "%g", d);
|
||||
break;
|
||||
case Arg_unsigned:
|
||||
printed = snprintf(buffer, space, "%u", u);
|
||||
break;
|
||||
case Arg_int:
|
||||
printed = snprintf(buffer, space, "%i", i);
|
||||
break;
|
||||
case Arg_cstr:
|
||||
printed = snprintf(buffer, space, "%s", c);
|
||||
break;
|
||||
case Arg_string:
|
||||
if(s)
|
||||
printed = snprintf(buffer, space, "%s", s->c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
if(printed > 0)
|
||||
return printed;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
//std::string format("format $1-5", args[0-4])
|
||||
//
|
||||
//Formats the fromat string into a std::string output
|
||||
//Arguments are placed into the string anywhere $1 through $5 are found
|
||||
//
|
||||
//For example:
|
||||
//format("test $1", 2.5f) returns "test 2.50000"
|
||||
//
|
||||
//Float, Double, unsigned, int, c string, and std::string arguments are supported
|
||||
//
|
||||
//There are no formatting options
|
||||
|
||||
struct FormatArg {
|
||||
enum ArgType {
|
||||
Arg_float,
|
||||
Arg_double,
|
||||
Arg_unsigned,
|
||||
Arg_int,
|
||||
Arg_cstr,
|
||||
Arg_string
|
||||
};
|
||||
|
||||
unsigned print(char* buffer, unsigned space);
|
||||
|
||||
FormatArg();
|
||||
FormatArg(float F);
|
||||
FormatArg(double D);
|
||||
FormatArg(unsigned U);
|
||||
FormatArg(int I);
|
||||
FormatArg(const char* C);
|
||||
FormatArg(const std::string& S);
|
||||
|
||||
unsigned type;
|
||||
union {
|
||||
float f;
|
||||
double d;
|
||||
unsigned u;
|
||||
int i;
|
||||
const char* c;
|
||||
const std::string* s;
|
||||
};
|
||||
};
|
||||
|
||||
void format(std::string& arg, const char* fmt, unsigned argn, FormatArg* argv);
|
||||
std::string format(const char* format, FormatArg a1);
|
||||
std::string format(const char* format, FormatArg a1, FormatArg a2);
|
||||
std::string format(const char* format, FormatArg a1, FormatArg a2, FormatArg a3);
|
||||
std::string format(const char* format, FormatArg a1, FormatArg a2, FormatArg a3, FormatArg a4);
|
||||
std::string format(const char* format, FormatArg a1, FormatArg a2, FormatArg a3, FormatArg a4, FormatArg a5);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
typedef double varInterpreter(void*,const std::string*);
|
||||
typedef double varIndexInterpreter(void*,int);
|
||||
typedef int varIndexConverter(const std::string*);
|
||||
double nullConverter(void*,const std::string*);
|
||||
|
||||
struct FormulaError {
|
||||
std::string msg;
|
||||
FormulaError(const char* message) : msg(message) {}
|
||||
FormulaError(std::string message) : msg(message) {}
|
||||
};
|
||||
|
||||
class Formula {
|
||||
public:
|
||||
//Evaluates the formula
|
||||
//Variables in the formula will be passed to <VarConverter>, which receives
|
||||
//the name of the variable, and the <UserPointer> passed to evaluate
|
||||
virtual double evaluate(varInterpreter VarConverter = nullConverter, void* UserPointer = nullptr, varIndexInterpreter VarIndexConverter = 0) = 0;
|
||||
virtual ~Formula() {}
|
||||
|
||||
//Creates a formula from infix notation
|
||||
//Can throw FormulaError if there is an error in the expression
|
||||
static Formula* fromInfix(const char* expression, varIndexConverter VarConverter = 0, bool catchErrors = true);
|
||||
};
|
||||
;
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "util/generic.h"
|
||||
#include "str_util.h"
|
||||
|
||||
Generic::Generic() : type(GT_Bool), check(false) {
|
||||
}
|
||||
|
||||
void Generic::fromString(const std::string& val) {
|
||||
switch(type) {
|
||||
case GT_Bool:
|
||||
check = toBool(val);
|
||||
break;
|
||||
case GT_Integer:
|
||||
num = toNumber<int>(val);
|
||||
break;
|
||||
case GT_Double:
|
||||
flt = toNumber<double>(val);
|
||||
break;
|
||||
case GT_String:
|
||||
*str = val;
|
||||
break;
|
||||
case GT_Enum: {
|
||||
value = 0;
|
||||
for(unsigned i = 0; i < values->size(); ++i) {
|
||||
if(val == (*values)[i]) {
|
||||
value = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Generic::toString() {
|
||||
switch(type) {
|
||||
default:
|
||||
case GT_Bool:
|
||||
return check ? "true" : "false";
|
||||
break;
|
||||
case GT_Integer:
|
||||
return ::toString(num);
|
||||
break;
|
||||
case GT_Double:
|
||||
return ::toString(flt,4);
|
||||
break;
|
||||
case GT_String:
|
||||
return *str;
|
||||
break;
|
||||
case GT_Enum:
|
||||
return (*values)[value];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Generic::~Generic() {
|
||||
if(type == GT_String)
|
||||
delete str;
|
||||
else if(type == GT_Enum)
|
||||
delete values;
|
||||
}
|
||||
|
||||
bool Generic::getBool() {
|
||||
return check;
|
||||
}
|
||||
|
||||
void Generic::setBool(bool val) {
|
||||
if(type == GT_String)
|
||||
delete str;
|
||||
else if(type == GT_Enum)
|
||||
delete values;
|
||||
type = GT_Bool;
|
||||
check = val;
|
||||
}
|
||||
|
||||
Generic::operator int() {
|
||||
return getInteger();
|
||||
}
|
||||
|
||||
Generic::operator double() {
|
||||
return getDouble();
|
||||
}
|
||||
|
||||
Generic::operator bool() {
|
||||
return getBool();
|
||||
}
|
||||
|
||||
void Generic::operator=(int v) {
|
||||
setInteger(v);
|
||||
}
|
||||
|
||||
void Generic::operator=(double v) {
|
||||
setDouble(v);
|
||||
}
|
||||
|
||||
void Generic::operator=(bool v) {
|
||||
setBool(v);
|
||||
}
|
||||
|
||||
void NamedGeneric::operator=(int v) {
|
||||
setInteger(v);
|
||||
}
|
||||
|
||||
void NamedGeneric::operator=(double v) {
|
||||
setDouble(v);
|
||||
}
|
||||
|
||||
void NamedGeneric::operator=(bool v) {
|
||||
setBool(v);
|
||||
}
|
||||
|
||||
int Generic::getInteger() {
|
||||
return num;
|
||||
}
|
||||
|
||||
void Generic::setInteger(int val) {
|
||||
if(type == GT_String)
|
||||
delete str;
|
||||
else if(type == GT_Enum)
|
||||
delete values;
|
||||
type = GT_Integer;
|
||||
num = val;
|
||||
}
|
||||
|
||||
double Generic::getDouble() {
|
||||
return flt;
|
||||
}
|
||||
|
||||
void Generic::setDouble(double val) {
|
||||
if(type == GT_String)
|
||||
delete str;
|
||||
else if(type == GT_Enum)
|
||||
delete values;
|
||||
type = GT_Double;
|
||||
flt = val;
|
||||
}
|
||||
|
||||
std::string* Generic::getString() {
|
||||
if(type == GT_String)
|
||||
return str;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Generic::setString(const std::string& val) {
|
||||
if(type == GT_String) {
|
||||
*str = val;
|
||||
}
|
||||
else {
|
||||
if(type == GT_Enum)
|
||||
delete values;
|
||||
type = GT_String;
|
||||
str = new std::string(val);
|
||||
}
|
||||
}
|
||||
|
||||
Generic::Generic(bool def) {
|
||||
type = GT_Bool;
|
||||
check = def;
|
||||
}
|
||||
|
||||
Generic::Generic(int def) {
|
||||
type = GT_Integer;
|
||||
num = def;
|
||||
}
|
||||
|
||||
Generic::Generic(const std::string& def) {
|
||||
type = GT_String;
|
||||
str = new std::string(def);
|
||||
}
|
||||
|
||||
Generic::Generic(double def) {
|
||||
type = GT_Double;
|
||||
flt = def;
|
||||
}
|
||||
|
||||
NamedGeneric::NamedGeneric(const std::string& Name, bool def) : Generic(def), name(Name) {
|
||||
}
|
||||
|
||||
NamedGeneric::NamedGeneric(const std::string& Name, int def) : Generic(def), name(Name) {
|
||||
}
|
||||
|
||||
NamedGeneric::NamedGeneric(const std::string& Name, const char* def) : Generic(std::string(def)), name(Name) {
|
||||
}
|
||||
|
||||
NamedGeneric::NamedGeneric(const std::string& Name, const std::string& def) : Generic(def), name(Name) {
|
||||
}
|
||||
|
||||
NamedGeneric::NamedGeneric(const std::string& Name, double def) : Generic(def), name(Name) {
|
||||
}
|
||||
|
||||
NamedGeneric::NamedGeneric() : Generic() {
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
enum GenericType {
|
||||
GT_Bool,
|
||||
GT_Integer,
|
||||
GT_Double,
|
||||
GT_Enum,
|
||||
GT_String,
|
||||
GT_Pointer,
|
||||
};
|
||||
|
||||
struct Generic {
|
||||
GenericType type;
|
||||
|
||||
union {
|
||||
bool check;
|
||||
std::string* str;
|
||||
void* ptr;
|
||||
struct {
|
||||
int num;
|
||||
int num_min;
|
||||
int num_max;
|
||||
};
|
||||
struct {
|
||||
double flt;
|
||||
double flt_min;
|
||||
double flt_max;
|
||||
};
|
||||
struct {
|
||||
std::vector<std::string>* values;
|
||||
int value;
|
||||
};
|
||||
};
|
||||
|
||||
void fromString(const std::string& str);
|
||||
std::string toString();
|
||||
|
||||
bool getBool();
|
||||
void setBool(bool val);
|
||||
|
||||
int getInteger();
|
||||
void setInteger(int val);
|
||||
|
||||
double getDouble();
|
||||
void setDouble(double val);
|
||||
|
||||
std::string* getString();
|
||||
void setString(const std::string& val);
|
||||
|
||||
void* getPtr();
|
||||
void setPtr(void* ptr);
|
||||
|
||||
operator int();
|
||||
operator double();
|
||||
operator bool();
|
||||
|
||||
void operator=(int);
|
||||
void operator=(double);
|
||||
void operator=(bool);
|
||||
|
||||
Generic(bool def);
|
||||
Generic(int def);
|
||||
Generic(const std::string& def);
|
||||
Generic(double def);
|
||||
|
||||
Generic();
|
||||
~Generic();
|
||||
};
|
||||
|
||||
struct NamedGeneric : Generic {
|
||||
std::string name;
|
||||
|
||||
NamedGeneric(const std::string& Name, bool def);
|
||||
NamedGeneric(const std::string& Name, int def);
|
||||
NamedGeneric(const std::string& Name, const char* def);
|
||||
NamedGeneric(const std::string& Name, const std::string& def);
|
||||
NamedGeneric(const std::string& Name, double def);
|
||||
|
||||
void operator=(int);
|
||||
void operator=(double);
|
||||
void operator=(bool);
|
||||
|
||||
NamedGeneric();
|
||||
};
|
||||
@@ -0,0 +1,340 @@
|
||||
#pragma once
|
||||
#include "vec2.h"
|
||||
#include "constants.h"
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
enum HexGridAdjacency {
|
||||
HEX_DownLeft,
|
||||
HEX_Down,
|
||||
HEX_DownRight,
|
||||
HEX_UpRight,
|
||||
HEX_Up,
|
||||
HEX_UpLeft,
|
||||
};
|
||||
|
||||
//Stores a hex grid layed out as:
|
||||
// Indices Positions
|
||||
//0,0 2,0 0.0,0.0 1.5,0.0
|
||||
// 1,0 0.75,0.5
|
||||
//0,1 2,1 0.0,1.0 1.5,1.0
|
||||
// 1,1 0.75,1.5
|
||||
//0,2 2,2 0.0,2.0 1.5,2.0
|
||||
|
||||
template<class T = bool>
|
||||
struct HexGrid {
|
||||
T* data;
|
||||
unsigned width, height;
|
||||
|
||||
HexGrid()
|
||||
: data(0) {
|
||||
}
|
||||
|
||||
HexGrid(vec2u size, T* Data)
|
||||
: data(Data), width(size.width), height(size.height)
|
||||
{
|
||||
}
|
||||
|
||||
HexGrid(vec2u size)
|
||||
: data(new T[size.width*size.height]), width(size.width), height(size.height)
|
||||
{
|
||||
}
|
||||
|
||||
HexGrid(unsigned Width, unsigned Height)
|
||||
: data(new T[Width*Height]), width(Width), height(Height)
|
||||
{
|
||||
}
|
||||
|
||||
HexGrid(const HexGrid<T>& other)
|
||||
: data(0), width(0), height(0)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
~HexGrid() {
|
||||
if(data)
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
HexGrid<T>& operator=(const HexGrid<T>& other) {
|
||||
resize(other.width, other.height);
|
||||
for(unsigned x = 0; x < width; ++x) {
|
||||
for(unsigned y = 0; y < height; ++y) {
|
||||
unsigned index = x * height + y;
|
||||
data[index] = other.data[index];
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void resize(unsigned Width, unsigned Height) {
|
||||
if(data)
|
||||
delete[] data;
|
||||
data = new T[Width*Height];
|
||||
width = Width;
|
||||
height = Height;
|
||||
}
|
||||
|
||||
void resize(vec2u size) {
|
||||
if(data)
|
||||
delete[] data;
|
||||
data = new T[size.width*size.height];
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
}
|
||||
|
||||
vec2u size() const {
|
||||
return vec2u(width, height);
|
||||
}
|
||||
|
||||
size_t length() const {
|
||||
return width * height;
|
||||
}
|
||||
|
||||
void clear(T value) {
|
||||
unsigned amount = width * height;
|
||||
for(unsigned i = 0; i < amount; ++i)
|
||||
data[i] = value;
|
||||
}
|
||||
|
||||
void zero() {
|
||||
memset(data, 0, sizeof(T) * width * height);
|
||||
}
|
||||
|
||||
unsigned count(T value) const {
|
||||
unsigned cnt = 0;
|
||||
unsigned amount = width * height;
|
||||
for(unsigned i = 0; i < amount; ++i)
|
||||
if(data[i] == value)
|
||||
++cnt;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
bool valid(const vec2u& pos) const {
|
||||
return pos.x < width && pos.y < height;
|
||||
}
|
||||
|
||||
bool valid(const vec2u& pos, HexGridAdjacency dir) const {
|
||||
vec2u p = pos;
|
||||
if(!advance(p.x, p.y, dir))
|
||||
return false;
|
||||
return p.x < width && p.y < height;
|
||||
}
|
||||
|
||||
T& get(unsigned x, unsigned y) {
|
||||
return data[x*height + y];
|
||||
}
|
||||
|
||||
const T& get(unsigned x, unsigned y) const {
|
||||
return data[x*height + y];
|
||||
}
|
||||
|
||||
T& get(unsigned x, unsigned y, HexGridAdjacency dir) {
|
||||
advance(x, y, dir);
|
||||
return data[x*height + y];
|
||||
}
|
||||
|
||||
const T& get(unsigned x, unsigned y, HexGridAdjacency dir) const {
|
||||
advance(x, y, dir);
|
||||
return data[x*height + y];
|
||||
}
|
||||
|
||||
T& get(vec2u pos) {
|
||||
return data[pos.x*height + pos.y];
|
||||
}
|
||||
|
||||
const T& get(vec2u pos) const {
|
||||
return data[pos.x*height + pos.y];
|
||||
}
|
||||
|
||||
T& get(vec2u pos, HexGridAdjacency dir) {
|
||||
advance(pos.x, pos.y, dir);
|
||||
return data[pos.x*height + pos.y];
|
||||
}
|
||||
|
||||
const T& get(vec2u pos, HexGridAdjacency dir) const {
|
||||
advance(pos.x, pos.y, dir);
|
||||
return data[pos.x*height + pos.y];
|
||||
}
|
||||
|
||||
const T& operator[](vec2u pos) const {
|
||||
return data[pos.x*height + pos.y];
|
||||
}
|
||||
|
||||
T& operator[](vec2u pos) {
|
||||
return data[pos.x*height + pos.y];
|
||||
}
|
||||
|
||||
const T& operator[](unsigned i) const {
|
||||
return data[i];
|
||||
}
|
||||
|
||||
T& operator[](unsigned i) {
|
||||
return data[i];
|
||||
}
|
||||
|
||||
static vec2d getEffectivePosition(unsigned x, unsigned y) {
|
||||
if(x % 2)
|
||||
return vec2d((double)x * 0.75, (double)y + 0.5);
|
||||
else
|
||||
return vec2d((double)x * 0.75, (double)y);
|
||||
}
|
||||
|
||||
static vec2d getEffectivePosition(const vec2u& pos) {
|
||||
if(pos.x % 2)
|
||||
return vec2d((double)pos.x * 0.75, (double)pos.y + 0.5);
|
||||
else
|
||||
return vec2d((double)pos.x * 0.75, (double)pos.y);
|
||||
}
|
||||
|
||||
static vec2i getGridPosition(const vec2d& pos) {
|
||||
vec2i out;
|
||||
|
||||
unsigned x = (unsigned)floor(pos.x / 0.75);
|
||||
double xoffset = pos.x - (x * 0.75);
|
||||
|
||||
//In a full tile
|
||||
if(xoffset > 0.25) {
|
||||
out.x = x;
|
||||
|
||||
if(x % 2 == 1)
|
||||
out.y = (int)floor(pos.y - 0.5);
|
||||
else
|
||||
out.y = (int)floor(pos.y);
|
||||
}
|
||||
else {
|
||||
unsigned y;
|
||||
double yoffset;
|
||||
|
||||
if(x % 2 == 1) {
|
||||
y = (unsigned)floor(pos.y - 0.5);
|
||||
yoffset = pos.y - y - 0.5;
|
||||
}
|
||||
else {
|
||||
y = (unsigned)floor(pos.y);
|
||||
yoffset = pos.y - y;
|
||||
}
|
||||
|
||||
if(yoffset < 0.5) {
|
||||
double linex = 0.25 - (0.5 * yoffset);
|
||||
|
||||
if(xoffset < linex) {
|
||||
out.x = x - 1;
|
||||
|
||||
if(x % 2 == 1)
|
||||
out.y = y;
|
||||
else
|
||||
out.y = y - 1;
|
||||
}
|
||||
else {
|
||||
out.x = x;
|
||||
out.y = y;
|
||||
}
|
||||
}
|
||||
else {
|
||||
double linex = 0.5 * (yoffset - 0.5);
|
||||
|
||||
if(xoffset < linex) {
|
||||
out.x = x - 1;
|
||||
|
||||
if(x % 2 == 1)
|
||||
out.y = y + 1;
|
||||
else
|
||||
out.y = y;
|
||||
}
|
||||
else {
|
||||
out.x = x;
|
||||
out.y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool advancePosition(vec2u& pos, const vec2u& size, HexGridAdjacency direction, unsigned amount = 1) {
|
||||
//Are we in the offset (+0.5 y) column? (1,3,5,etc)
|
||||
bool offset = (pos.x % 2) != 0;
|
||||
|
||||
int moveY = 0, moveX = 0;
|
||||
|
||||
switch(direction) {
|
||||
case HEX_Up:
|
||||
moveY = -1;
|
||||
break;
|
||||
|
||||
case HEX_UpLeft:
|
||||
moveX = -1;
|
||||
if(!offset)
|
||||
moveY = -1;
|
||||
break;
|
||||
|
||||
case HEX_UpRight:
|
||||
moveX = 1;
|
||||
if(!offset)
|
||||
moveY = -1;
|
||||
break;
|
||||
|
||||
case HEX_DownLeft:
|
||||
moveX = -1;
|
||||
if(offset)
|
||||
moveY = 1;
|
||||
break;
|
||||
|
||||
case HEX_Down:
|
||||
moveY = 1;
|
||||
break;
|
||||
|
||||
case HEX_DownRight:
|
||||
moveX = 1;
|
||||
if(offset)
|
||||
moveY = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
bool inBounds = true;
|
||||
if(moveY) {
|
||||
unsigned newY = pos.y + (unsigned)moveY * amount;
|
||||
if(newY >= size.height)
|
||||
inBounds = false;
|
||||
else
|
||||
pos.y = newY;
|
||||
}
|
||||
|
||||
if(moveX) {
|
||||
unsigned newX = pos.x + (unsigned)moveX * amount;
|
||||
if(newX >= size.width)
|
||||
inBounds = false;
|
||||
else
|
||||
pos.x = newX;
|
||||
}
|
||||
|
||||
return inBounds;
|
||||
}
|
||||
|
||||
//Advances <x,y> toward <direction>
|
||||
//Returns false if the result would be out of bounds of the grid
|
||||
//If only one dimension is a valid destination, it will move in that dimension
|
||||
bool advance(unsigned& x, unsigned& y, HexGridAdjacency direction, unsigned amount = 1) const {
|
||||
vec2u pos(x, y);
|
||||
bool val = advancePosition(pos, vec2u(width, height), direction, amount);
|
||||
x = pos.x;
|
||||
y = pos.y;
|
||||
return val;
|
||||
}
|
||||
|
||||
bool advance(vec2u& pos, HexGridAdjacency direction, unsigned amount = 1) const {
|
||||
return advancePosition(pos, vec2u(width, height), direction, amount);
|
||||
}
|
||||
|
||||
static HexGridAdjacency AdjacencyFromRadians(double radians) {
|
||||
while(radians < 0)
|
||||
radians += twopi;
|
||||
int dir =((int)floor(radians / (pi / 3.0)) + 3) % 6;
|
||||
return HexGridAdjacency(dir);
|
||||
}
|
||||
|
||||
static double RadiansFromAdjacency(HexGridAdjacency adj) {
|
||||
return ((double)(adj) - 3.0) * (pi / 3.0) + (twopi / 6.0 * 0.5);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,601 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
enum LinkContainerBehavior {
|
||||
LCB_Unordered,
|
||||
LCB_Ordered,
|
||||
};
|
||||
|
||||
/* LinkContainer
|
||||
* -------------
|
||||
* Base type for a set of containers that are thread-safe to read from.
|
||||
* Reads can occur at the same time as writes, but writes must not happen simultaneously!
|
||||
*
|
||||
* Intended for use with small data structures that need to be read from multiple threads,
|
||||
* and can change but do so infrequently enough that the extra write overhead is negligible.
|
||||
*
|
||||
* CAVEATS:
|
||||
* - Will never shrink in size. Seriously, only use for small sets.
|
||||
* - Iterating over it is sometimes going to skip an element.
|
||||
* - Getting by index is sometimes going to return null even if index < size.
|
||||
* */
|
||||
template<typename T, unsigned char PoolSize, LinkContainerBehavior Behavior>
|
||||
class LinkContainer {
|
||||
protected:
|
||||
void* start;
|
||||
unsigned int count;
|
||||
|
||||
struct PoolHeader {
|
||||
unsigned char filledElements;
|
||||
bool contiguous;
|
||||
void* next;
|
||||
};
|
||||
|
||||
struct PoolElem {
|
||||
bool filled;
|
||||
T data;
|
||||
};
|
||||
|
||||
inline PoolHeader& getHeader(void* mem) const {
|
||||
return *(PoolHeader*)mem;
|
||||
}
|
||||
|
||||
inline PoolElem& getElem(void* mem, unsigned char index) const {
|
||||
unsigned char* dataStart = ((unsigned char*)mem) + sizeof(PoolHeader);
|
||||
unsigned char* elemData = dataStart + (sizeof(PoolElem) * index);
|
||||
return *(PoolElem*)elemData;
|
||||
}
|
||||
|
||||
inline PoolElem& getElem(void* at) const
|
||||
{
|
||||
return *(PoolElem*)at;
|
||||
}
|
||||
|
||||
inline void* allocate(const T& data) {
|
||||
auto size = sizeof(PoolHeader) + sizeof(PoolElem) * PoolSize;
|
||||
void* pool = malloc(size);
|
||||
memset(pool, 0, size);
|
||||
auto& header = getHeader(pool);
|
||||
header.filledElements = 1;
|
||||
header.contiguous = true;
|
||||
auto& elem = getElem(pool, 0);
|
||||
elem.filled = true;
|
||||
elem.data = data;
|
||||
return pool;
|
||||
}
|
||||
|
||||
inline void* getLast() const {
|
||||
void* pool = start;
|
||||
while(pool)
|
||||
{
|
||||
auto& header = getHeader(pool);
|
||||
if(header.next)
|
||||
pool = header.next;
|
||||
else
|
||||
return pool;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void checkContiguous(void* pool) {
|
||||
auto& header = getHeader(pool);
|
||||
bool foundEmpty = false;
|
||||
for(unsigned char i = 0; i < header.filledElements; ++i) {
|
||||
if(!getElem(pool, i).filled) {
|
||||
foundEmpty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
header.contiguous = !foundEmpty;
|
||||
}
|
||||
|
||||
void getIndex(unsigned int index, void*& outPool, unsigned char& outElem) const {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
auto& header = getHeader(pool);
|
||||
unsigned char cnt = header.filledElements;
|
||||
if(index < cnt) {
|
||||
if(header.contiguous)
|
||||
{
|
||||
auto& elem = getElem(pool, index);
|
||||
if(elem.filled)
|
||||
{
|
||||
outPool = pool;
|
||||
outElem = index;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned char i = 0; i < PoolSize; ++i)
|
||||
{
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled) {
|
||||
if(index == 0) {
|
||||
outPool = pool;
|
||||
outElem = i;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
index -= cnt;
|
||||
}
|
||||
pool = header.next;
|
||||
}
|
||||
outPool = nullptr;
|
||||
outElem = -1;
|
||||
}
|
||||
|
||||
public:
|
||||
LinkContainer()
|
||||
: start(nullptr), count(0) {
|
||||
}
|
||||
|
||||
~LinkContainer() {
|
||||
auto* pool = start;
|
||||
while(pool) {
|
||||
void* next = getHeader(pool).next;
|
||||
free(pool);
|
||||
pool = next;
|
||||
}
|
||||
|
||||
start = nullptr;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/* Add a new element to the container. If Behavior was set to Ordered, this
|
||||
* will insert at the end. If not, it will insert at an arbitrary point in
|
||||
* the container. */
|
||||
void add(const T& data) {
|
||||
// Simple case, we are empty
|
||||
if(start == nullptr) {
|
||||
start = allocate(data);
|
||||
count++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through pools and see what to do
|
||||
if(Behavior == LCB_Unordered) {
|
||||
// See if we have an existing pool we can insert into
|
||||
void* pool = start;
|
||||
while (pool) {
|
||||
auto& header = getHeader(pool);
|
||||
if(header.filledElements < PoolSize) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(!elem.filled) {
|
||||
header.filledElements++;
|
||||
elem.data = data;
|
||||
elem.filled = true;
|
||||
count++;
|
||||
if(!header.contiguous) {
|
||||
if(header.filledElements == PoolSize || header.filledElements == i+1)
|
||||
header.contiguous = true;
|
||||
else
|
||||
checkContiguous(pool);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(header.next) {
|
||||
pool = header.next;
|
||||
}
|
||||
else {
|
||||
// Create a new pool at the end
|
||||
header.next = allocate(data);
|
||||
count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else /*if(Behavior == LCB_Ordered)*/
|
||||
{
|
||||
// We can only insert into the last pool we have, otherwise we need to create a new one
|
||||
auto* pool = getLast();
|
||||
auto& header = getHeader(pool);
|
||||
if(header.filledElements < PoolSize) {
|
||||
unsigned char lastFilled = (unsigned char)-1;
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, PoolSize - i - 1);
|
||||
if(elem.filled) {
|
||||
lastFilled = PoolSize - i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(lastFilled + 1 >= PoolSize) {
|
||||
// Must create a new pool
|
||||
header.next = allocate(data);
|
||||
count++;
|
||||
}
|
||||
else {
|
||||
// Insert one past the last filled element
|
||||
auto& elem = getElem(pool, lastFilled + 1);
|
||||
header.filledElements++;
|
||||
elem.data = data;
|
||||
elem.filled = true;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(const T& data) const {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled && elem.data == data)
|
||||
return true;
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void removeAt(unsigned int index) {
|
||||
void* pool;
|
||||
unsigned char elem;
|
||||
getIndex(index, pool, elem);
|
||||
|
||||
if(pool == nullptr || elem < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
getElem(pool, elem).filled = false;
|
||||
getHeader(pool).filledElements--;
|
||||
count--;
|
||||
checkContiguous(pool);
|
||||
}
|
||||
|
||||
int removeAll(const T& value) {
|
||||
int removedCount = 0;
|
||||
void* pool = start;
|
||||
while (pool) {
|
||||
auto& header = getHeader(pool);
|
||||
bool removed = false;
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled) {
|
||||
if(elem.data == value) {
|
||||
elem.filled = false;
|
||||
removed = true;
|
||||
header.filledElements--;
|
||||
count--;
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(removed)
|
||||
checkContiguous(pool);
|
||||
pool = header.next;
|
||||
}
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
void* pool = start;
|
||||
while (pool) {
|
||||
auto& header = getHeader(pool);
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled) {
|
||||
header.contiguous = false;
|
||||
elem.filled = false;
|
||||
header.filledElements--;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
pool = header.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that due to the threaded nature of this data structure,
|
||||
* this may very well return a nullptr even if index < count,
|
||||
* so make sure to always check.
|
||||
*
|
||||
* It can also of course return a stale (already removed) value,
|
||||
* and iteration might temporarily miss an element that was there before.
|
||||
*
|
||||
* The data structure is guaranteed not to segfault from threaded use, but
|
||||
* value may be slightly wrong sometimes.
|
||||
*/
|
||||
T* getAt(unsigned int index) const {
|
||||
void* pool;
|
||||
unsigned char elem;
|
||||
getIndex(index, pool, elem);
|
||||
if(pool != nullptr && elem >= 0)
|
||||
{
|
||||
return &getElem(pool, elem).data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned int size() const {
|
||||
return count;
|
||||
}
|
||||
|
||||
template<typename CB>
|
||||
void iterateAll(CB cb) const {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled)
|
||||
cb(elem.data);
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, int PoolSize = 16>
|
||||
class LinkArray : LinkContainer<T, PoolSize, LCB_Ordered> {};
|
||||
|
||||
template<typename T, int PoolSize = 16>
|
||||
class LinkBucket : LinkContainer<T, PoolSize, LCB_Unordered> {};
|
||||
|
||||
/**
|
||||
* A very simple O(n) map structure for int64 -> int64/double.
|
||||
*
|
||||
* Should be used for very small maps when the set of keys changes rarely, and
|
||||
* threaded reading of key/value pairs is worth the key lookup and change
|
||||
* overhead.
|
||||
*
|
||||
* Also directly supports value delta tracking.
|
||||
*/
|
||||
struct LinkMapElem {
|
||||
uint64_t key;
|
||||
bool dirty;
|
||||
union {
|
||||
uint64_t value;
|
||||
double doubleValue;
|
||||
};
|
||||
union {
|
||||
uint64_t prevValue;
|
||||
double prevDoubleValue;
|
||||
};
|
||||
};
|
||||
|
||||
template<int PoolSize = 16>
|
||||
class LinkMapBase : LinkContainer<struct LinkMapElem, 16, LCB_Unordered> {
|
||||
private:
|
||||
unsigned int dirtyCount;
|
||||
union {
|
||||
uint64_t defaultInt;
|
||||
double defaultDouble;
|
||||
};
|
||||
|
||||
inline LinkMapElem* getMapElem(uint64_t key) const {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled && elem.data.key == key)
|
||||
return &elem.data;
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T, bool isDouble>
|
||||
inline void setTyped(uint64_t key, uint64_t value, T dirtyResolution) {
|
||||
void* emptyElem = nullptr;
|
||||
void* emptyPool = nullptr;
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled) {
|
||||
if(elem.data.key == key) {
|
||||
if(!elem.data.dirty && dirtyResolution >= 0) {
|
||||
if(dirtyResolution == 0) {
|
||||
elem.data.dirty = true;
|
||||
}
|
||||
else if(isDouble) {
|
||||
elem.data.value = value;
|
||||
if(fabs(elem.data.doubleValue - elem.data.prevDoubleValue) >= dirtyResolution) {
|
||||
elem.data.dirty = true;
|
||||
dirtyCount++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
elem.data.value = value;
|
||||
if(llabs((int64_t)elem.data.value - (int64_t)elem.data.prevValue) >= dirtyResolution) {
|
||||
elem.data.dirty = true;
|
||||
dirtyCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
elem.data.value = value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
emptyElem = &elem;
|
||||
emptyPool = pool;
|
||||
}
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
|
||||
if(emptyElem) {
|
||||
auto& header = getHeader(emptyPool);
|
||||
header.filledElements++;
|
||||
auto& elem = getElem(emptyElem);
|
||||
elem.data.key = key;
|
||||
elem.data.dirty = true;
|
||||
elem.data.value = value;
|
||||
elem.data.prevValue = value;
|
||||
elem.filled = true;
|
||||
|
||||
count++;
|
||||
dirtyCount++;
|
||||
}
|
||||
else {
|
||||
auto* pool = getLast();
|
||||
|
||||
LinkMapElem newElem;
|
||||
newElem.key = key;
|
||||
newElem.dirty = true;
|
||||
newElem.value = value;
|
||||
newElem.prevValue = value;
|
||||
if(pool)
|
||||
getHeader(pool).next = allocate(newElem);
|
||||
else
|
||||
start = allocate(newElem);
|
||||
|
||||
count++;
|
||||
dirtyCount++;
|
||||
}
|
||||
}
|
||||
public:
|
||||
LinkMapBase() : defaultInt(0), dirtyCount(0) {
|
||||
}
|
||||
|
||||
LinkMapBase(uint64_t defaultValue) : defaultInt(defaultValue), dirtyCount(0) {
|
||||
}
|
||||
|
||||
LinkMapBase(double defaultValue) : defaultDouble(defaultValue), dirtyCount(0) {
|
||||
}
|
||||
|
||||
void setDefaultValue(uint64_t newValue) {
|
||||
defaultInt = newValue;
|
||||
}
|
||||
|
||||
uint64_t getDefaultValue() const {
|
||||
return defaultInt;
|
||||
}
|
||||
|
||||
void setDefaultDouble(double newValue) {
|
||||
defaultDouble = newValue;
|
||||
}
|
||||
|
||||
double getDefaultDouble() const {
|
||||
return defaultDouble;
|
||||
}
|
||||
|
||||
unsigned int size() const {
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t getKeyAtIndex(unsigned int index) const {
|
||||
if(auto* elem = getAt(index))
|
||||
return elem->key;
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t getAtIndex(unsigned int index) const {
|
||||
if(auto* elem = getAt(index))
|
||||
return elem->value;
|
||||
return defaultInt;
|
||||
}
|
||||
|
||||
double getDoubleAtIndex(unsigned int index) const {
|
||||
if(auto* elem = getAt(index))
|
||||
return elem->doubleValue;
|
||||
return defaultDouble;
|
||||
}
|
||||
|
||||
uint64_t get(uint64_t key) const {
|
||||
auto* elem = getMapElem(key);
|
||||
if(elem)
|
||||
return elem->value;
|
||||
else
|
||||
return defaultInt;
|
||||
}
|
||||
|
||||
double getDouble(uint64_t key) const {
|
||||
auto* elem = getMapElem(key);
|
||||
if(elem)
|
||||
return elem->doubleValue;
|
||||
else
|
||||
return defaultDouble;
|
||||
}
|
||||
|
||||
template<typename CB>
|
||||
void iterateAll(CB cb) const {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled)
|
||||
cb(elem.data.key, elem.data.value);
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename CB>
|
||||
void iterateDirty(CB cb) const {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled && elem.data.dirty)
|
||||
cb(elem.data.key, elem.data.value);
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename CB>
|
||||
void handleDirty(CB cb) {
|
||||
void* pool = start;
|
||||
while(pool) {
|
||||
for(unsigned char i = 0; i < PoolSize; ++i) {
|
||||
auto& elem = getElem(pool, i);
|
||||
if(elem.filled && elem.data.dirty) {
|
||||
if(cb(elem.data.key, elem.data.value)) {
|
||||
elem.data.prevValue = elem.data.value;
|
||||
elem.data.dirty = false;
|
||||
dirtyCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
pool = getHeader(pool).next;
|
||||
}
|
||||
}
|
||||
|
||||
bool getDirtyCount() const {
|
||||
return dirtyCount;
|
||||
}
|
||||
|
||||
bool hasDirty() const {
|
||||
return dirtyCount != 0;
|
||||
}
|
||||
|
||||
bool isDirty(uint64_t key) const {
|
||||
auto* elem = getMapElem(key);
|
||||
return elem != nullptr && elem->dirty;
|
||||
}
|
||||
|
||||
bool contains(uint64_t key) const {
|
||||
return getMapElem(key) != nullptr;
|
||||
}
|
||||
|
||||
void set(uint64_t key, uint64_t value, int64_t dirtyResolution = 0) {
|
||||
setTyped<int64_t,true>(key, value, dirtyResolution);
|
||||
}
|
||||
|
||||
void setDouble(uint64_t key, double value, double dirtyResolution = 0.0) {
|
||||
setTyped<double,true>(key, reinterpret_cast<uint64_t&>(value), dirtyResolution);
|
||||
}
|
||||
};
|
||||
|
||||
typedef LinkMapBase<> LinkMap;
|
||||
@@ -0,0 +1,225 @@
|
||||
#include "threads.h"
|
||||
|
||||
template<class T>
|
||||
struct LockedType {
|
||||
threads::Mutex lock;
|
||||
T value;
|
||||
|
||||
LockedType() : value() {}
|
||||
LockedType(T v) : value(v) {}
|
||||
LockedType(const LockedType& other) : value(other.value) {}
|
||||
~LockedType() {}
|
||||
|
||||
T operator+=(T v) {
|
||||
lock.lock();
|
||||
T r = value + v;
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator-=(T v) {
|
||||
lock.lock();
|
||||
T r = value - v;
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator*=(T v) {
|
||||
lock.lock();
|
||||
T r = value * v;
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator/=(T v) {
|
||||
lock.lock();
|
||||
T r = value / v;
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator|=(T v) {
|
||||
lock.lock();
|
||||
T r = value | v;
|
||||
value = v;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator^=(T v) {
|
||||
lock.lock();
|
||||
T r = value ^ v;
|
||||
value = v;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator&=(T v) {
|
||||
lock.lock();
|
||||
T r = value & v;
|
||||
value = v;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T operator=(T v) {
|
||||
lock.lock();
|
||||
value = v;
|
||||
lock.release();
|
||||
return v;
|
||||
}
|
||||
|
||||
T minimum(T v) {
|
||||
lock.lock();
|
||||
T r = v < value ? v : value;
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T maximum(T v) {
|
||||
lock.lock();
|
||||
T r = v > value ? v : value;
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T avg(T v) {
|
||||
lock.lock();
|
||||
T r = (T)( ((double)v + (double)value) * 0.5 );
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T consume(T amount) {
|
||||
lock.lock();
|
||||
T take;
|
||||
if(value < amount)
|
||||
if(value > 0)
|
||||
take = value;
|
||||
else
|
||||
take = 0;
|
||||
else
|
||||
take = amount;
|
||||
value -= take;
|
||||
lock.release();
|
||||
return take;
|
||||
}
|
||||
|
||||
T interp(T toward, double percent) {
|
||||
lock.lock();
|
||||
T r = (T)( ((double)(toward - value) * percent) + (double)toward );
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
|
||||
T toggle() {
|
||||
lock.lock();
|
||||
T r = value == 0 ? T(1) : T(0);
|
||||
value = r;
|
||||
lock.release();
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct LockedHandle {
|
||||
//Define an invalid pointer as a 'locked' state for a handle. Should be
|
||||
//faster than a full mutex and takes less memory.
|
||||
static const unsigned spinCount = 5;
|
||||
static const size_t INVALID_PTR = (size_t)-1;
|
||||
mutable void* value;
|
||||
|
||||
LockedHandle() : value(0) {}
|
||||
LockedHandle(T* v) : value(0) { set(v); }
|
||||
LockedHandle(const LockedHandle<T>& other) : value(0) { set(other.get()); }
|
||||
~LockedHandle() { set(0); }
|
||||
|
||||
T* acquire() const {
|
||||
void* ptr = value;
|
||||
int spins = 0;
|
||||
while(ptr == (void*)INVALID_PTR || threads::compare_and_swap(&value, ptr, (void*)INVALID_PTR) != ptr) {
|
||||
ptr = value;
|
||||
|
||||
++spins;
|
||||
if(spins == spinCount) {
|
||||
threads::sleep(0);
|
||||
spins = 0;
|
||||
}
|
||||
}
|
||||
return (T*)ptr;
|
||||
}
|
||||
|
||||
void release(T* ptr) const {
|
||||
value = ptr;
|
||||
}
|
||||
|
||||
LockedHandle& operator=(T* value) {
|
||||
set(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//Get the pointer value
|
||||
// Will grab a refenence before returning,
|
||||
// so make sure you release it when done.
|
||||
T* get() const {
|
||||
T* ptr = acquire();
|
||||
if(ptr)
|
||||
ptr->grab();
|
||||
release(ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
//Safe get for when it's assured no writes are taking
|
||||
//place. Still needs to avoid other reads, though.
|
||||
T* get_safe() const {
|
||||
T* ptr;
|
||||
do {
|
||||
ptr = (T*)value;
|
||||
}
|
||||
while(ptr == (T*)INVALID_PTR);
|
||||
|
||||
if(ptr)
|
||||
ptr->grab();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
//Set the value, keeping all the reference stuff valid.
|
||||
void set(T* ptr) {
|
||||
if(ptr)
|
||||
ptr->grab();
|
||||
set_withref(ptr);
|
||||
}
|
||||
|
||||
void set_withref(T* ptr) {
|
||||
void* newval = (void*)ptr;
|
||||
void* oldval = (void*)value;
|
||||
int spins = 0;
|
||||
while(true) {
|
||||
if(oldval == (void*)INVALID_PTR) {
|
||||
oldval = value;
|
||||
|
||||
++spins;
|
||||
if(spins == spinCount) {
|
||||
threads::sleep(0);
|
||||
spins = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
void* res = threads::compare_and_swap(&value, oldval, newval);
|
||||
if(res == oldval)
|
||||
break;
|
||||
oldval = res;
|
||||
};
|
||||
ptr = (T*)oldval;
|
||||
if(ptr)
|
||||
ptr->drop();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
#include <functional>
|
||||
|
||||
//Implements a 'lockless' data type. Operations can still
|
||||
//take longer amounts of time depending on concurrency, but
|
||||
//are not as expensive as a full locked type.
|
||||
#define LOCKLESS_PRE \
|
||||
union { swappable s; T real; } local, stored;\
|
||||
stored.s = swap;\
|
||||
local.s = 0;\
|
||||
T& previous = stored.real;\
|
||||
do {
|
||||
|
||||
#define LOCKLESS_POST\
|
||||
swappable chk = threads::compare_and_swap(&swap, stored.s, local.s);\
|
||||
if(chk == stored.s)\
|
||||
break;\
|
||||
stored.s = chk;\
|
||||
}\
|
||||
while (true);
|
||||
|
||||
template<class T, class swappable = long long>
|
||||
struct LocklessType {
|
||||
static_assert(sizeof(T) <= sizeof(swappable), "type too big");
|
||||
union {
|
||||
swappable swap;
|
||||
T value;
|
||||
};
|
||||
|
||||
LocklessType() : swap(0) {}
|
||||
LocklessType(T v) : swap(0) { set(v); }
|
||||
LocklessType(const LocklessType& other) : value(other.value) {}
|
||||
~LocklessType() {}
|
||||
|
||||
//Atomically perform an operation on the value
|
||||
//The operation should be pure (no side effects) and
|
||||
//efficient (can be executed multiple times viably).
|
||||
// Returns the new value that was placed.
|
||||
T set(const std::function<T(T)>& operation) {
|
||||
union { swappable s; T real; } local, stored;
|
||||
stored.s = swap;
|
||||
local.s = 0;
|
||||
do {
|
||||
local.real = operation(stored.real);
|
||||
swappable chk = threads::compare_and_swap(&swap, stored.s, local.s);
|
||||
if(chk == stored.s)
|
||||
break;
|
||||
stored.s = chk;
|
||||
} while(true);
|
||||
return stored.s;
|
||||
}
|
||||
|
||||
T get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
T set(T v) {
|
||||
value = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
operator T() {
|
||||
return get();
|
||||
}
|
||||
|
||||
T operator+=(T v) {
|
||||
return set([v](T p) -> T { return p + v; });
|
||||
}
|
||||
|
||||
T operator-=(T v) {
|
||||
return set([v](T p) -> T { return p - v; });
|
||||
}
|
||||
|
||||
T operator*=(T v) {
|
||||
return set([v](T p) -> T { return p * v; });
|
||||
}
|
||||
|
||||
T operator/=(T v) {
|
||||
return set([v](T p) -> T { return p / v; });
|
||||
}
|
||||
|
||||
T operator|=(T v) {
|
||||
return set([v](T p) -> T { return p | v; });
|
||||
}
|
||||
|
||||
T operator^=(T v) {
|
||||
return set([v](T p) -> T { return p ^ v; });
|
||||
}
|
||||
|
||||
T operator&=(T v) {
|
||||
return set([v](T p) -> T { return p & v; });
|
||||
}
|
||||
|
||||
T operator=(T v) {
|
||||
return set(v);
|
||||
}
|
||||
|
||||
T operator|(T v) {
|
||||
return value | v;
|
||||
}
|
||||
|
||||
T operator&(T v) {
|
||||
return value & v;
|
||||
}
|
||||
|
||||
T minimum(T v) {
|
||||
return set([v](T p) -> T { return v < p ? v : p; });
|
||||
}
|
||||
|
||||
T maximum(T v) {
|
||||
return set([v](T p) -> T { return v > p ? v : p; });
|
||||
}
|
||||
|
||||
T avg(T v) {
|
||||
return set([v](T p) -> T { return (T)(((double)v + (double)p) * 0.5); });
|
||||
}
|
||||
|
||||
T consume(T amount) {
|
||||
T take;
|
||||
set([&](T p) -> T {
|
||||
if(p < amount)
|
||||
if(p > 0)
|
||||
take = p;
|
||||
else
|
||||
take = 0;
|
||||
else
|
||||
take = amount;
|
||||
return p - take;
|
||||
});
|
||||
return take;
|
||||
}
|
||||
|
||||
T interp(T toward, double percent) {
|
||||
return set([&](T p) -> T {
|
||||
return (T)(((double)(toward - p) * percent) + (double)toward);
|
||||
});
|
||||
}
|
||||
|
||||
T toggle() {
|
||||
return set([&](T p) -> T {
|
||||
return p == 0 ? (T)1 : (T)0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
typedef LocklessType<int,int> LocklessInt;
|
||||
typedef LocklessType<float,int> LocklessFloat;
|
||||
typedef LocklessType<double,long long> LocklessDouble;
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "mesh.h"
|
||||
#include "constants.h"
|
||||
|
||||
Mesh* generateSphereMesh(unsigned int vertical, unsigned int horizontal) {
|
||||
if(vertical < 2)
|
||||
vertical = 2;
|
||||
if(horizontal < 2)
|
||||
horizontal = 2;
|
||||
|
||||
float vf = (float)vertical, hf = (float)horizontal;
|
||||
|
||||
Mesh& mesh = *(new Mesh());
|
||||
mesh.vertices.reserve((vertical+1) * (horizontal+1));
|
||||
|
||||
//v == 0
|
||||
for(unsigned int h = 0; h <= horizontal; ++h) {
|
||||
Vertex vert;
|
||||
|
||||
vert.position = vec3f(0,1,0);
|
||||
vert.normal = vert.position;
|
||||
vert.u = (float)h/hf;
|
||||
vert.v = 0;
|
||||
|
||||
mesh.vertices.push_back(vert);
|
||||
}
|
||||
|
||||
for(unsigned int v = 1; v < vertical; ++v) {
|
||||
float sinz = (float)sin(pi/2.f - (pi * (float)v/vf));
|
||||
float cosz = (float)cos(pi/2.f - (pi * (float)v/vf));
|
||||
|
||||
for(unsigned int h = 0; h <= horizontal; ++h) {
|
||||
float angle = (float)twopi * (float)(h % horizontal)/hf; //Force angle on both ends to be identical
|
||||
Vertex vert;
|
||||
|
||||
vert.position = vec3f(cos(angle) * cosz, sinz, sin(angle) * cosz);
|
||||
vert.normal = vert.position;
|
||||
vert.u = (float)h/hf;
|
||||
vert.v = (float)v/vf;
|
||||
|
||||
mesh.vertices.push_back(vert);
|
||||
}
|
||||
}
|
||||
|
||||
//v == vertical
|
||||
for(unsigned int h = 0; h <= horizontal; ++h) {
|
||||
Vertex vert;
|
||||
|
||||
vert.position = vec3f(0,-1,0);
|
||||
vert.normal = vert.position;
|
||||
vert.u = (float)h/hf;
|
||||
vert.v = 1.f;
|
||||
|
||||
mesh.vertices.push_back(vert);
|
||||
}
|
||||
|
||||
mesh.faces.reserve((2 * (vertical-2) * horizontal) + (2 * horizontal));
|
||||
for(unsigned int v = 0; v < vertical; ++v) {
|
||||
unsigned int stride = horizontal+1, line = v * stride, nextline = (v+1) * stride;
|
||||
for(unsigned int h = 0; h < horizontal; ++h) {
|
||||
if(v != 0)
|
||||
mesh.faces.push_back(Mesh::Face(h+line,h+line+1,h+nextline));
|
||||
if(v != vertical-1)
|
||||
mesh.faces.push_back(Mesh::Face(h+line+1,h+nextline+1,h+nextline));
|
||||
}
|
||||
}
|
||||
|
||||
return &mesh;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
struct Mesh;
|
||||
|
||||
Mesh* generateSphereMesh(unsigned int vertical, unsigned int horizontal);
|
||||
@@ -0,0 +1,207 @@
|
||||
#include "name_generator.h"
|
||||
#include "compat/misc.h"
|
||||
#include "str_util.h"
|
||||
#include "util/random.h"
|
||||
#include <fstream>
|
||||
|
||||
#ifndef NAMEGEN_MUTATE_END_PCT
|
||||
#define NAMEGEN_MUTATE_END_PCT 0.1
|
||||
#endif
|
||||
|
||||
NameGenerator::NameGenerator() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void NameGenerator::clear() {
|
||||
data.clear();
|
||||
names.clear();
|
||||
nameStarts.clear();
|
||||
usedNames.clear();
|
||||
preventDuplicates = false;
|
||||
useGeneration = true;
|
||||
mutationChance = 0;
|
||||
}
|
||||
|
||||
void NameGenerator::read(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
skipBOM(file);
|
||||
if(file.is_open()) {
|
||||
while(true) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
if(file.fail())
|
||||
break;
|
||||
line = trim(line);
|
||||
if(line.size() > 0)
|
||||
addName(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NameGenerator::write(const std::string& filename) {
|
||||
std::ofstream file(filename);
|
||||
foreach(it, names)
|
||||
file << *it << "\n";
|
||||
}
|
||||
|
||||
static int randomchar(bool start = false) {
|
||||
if(start) {
|
||||
return randomi((int)'A', (int)'Z');
|
||||
}
|
||||
else {
|
||||
if(randomf() < NAMEGEN_MUTATE_END_PCT)
|
||||
return 0;
|
||||
return randomi((int)'a', (int)'z');
|
||||
}
|
||||
}
|
||||
|
||||
std::string NameGenerator::generate() {
|
||||
if(names.size() == 0)
|
||||
return "Error";
|
||||
if(!useGeneration)
|
||||
return names[randomi(0, names.size() - 1)];
|
||||
|
||||
std::string name;
|
||||
|
||||
while(true) {
|
||||
std::pair<int, int> syl;
|
||||
name = "";
|
||||
|
||||
//Add the start of a name
|
||||
if(mutationChance != 0 && randomf() < mutationChance) {
|
||||
syl.first = randomchar(true);
|
||||
syl.second = randomchar();
|
||||
u8append(name, syl.first);
|
||||
u8append(name, syl.second);
|
||||
}
|
||||
else {
|
||||
int chance = randomi(0, names.size() - 1);
|
||||
int cum = 0;
|
||||
foreach(it, nameStarts) {
|
||||
cum += it->second;
|
||||
if(chance < cum) {
|
||||
syl.first = it->first.first;
|
||||
syl.second = it->first.second;
|
||||
u8append(name, syl.first);
|
||||
u8append(name, syl.second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Continue on
|
||||
int next = 0;
|
||||
do {
|
||||
auto it = data.find(syl);
|
||||
if(it == data.end())
|
||||
break;
|
||||
ProbData& pd = it->second;
|
||||
|
||||
if(mutationChance != 0 && randomf() < mutationChance) {
|
||||
next = randomchar();
|
||||
syl.first = syl.second;
|
||||
syl.second = next;
|
||||
if(next)
|
||||
u8append(name, next);
|
||||
}
|
||||
else {
|
||||
int chance = randomi(0, pd.occurances - 1);
|
||||
int cum = 0;
|
||||
foreach(it, pd.nextCount) {
|
||||
cum += it->second;
|
||||
if(chance < cum) {
|
||||
next = it->first;
|
||||
syl.first = syl.second;
|
||||
syl.second = next;
|
||||
if(next)
|
||||
u8append(name, next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(next);
|
||||
|
||||
//Do duplicate prevention
|
||||
if(!preventDuplicates)
|
||||
return name;
|
||||
|
||||
if(usedNames.find(name) == usedNames.end()) {
|
||||
usedNames.insert(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NameGenerator::hasName(const std::string& name) {
|
||||
foreach(it, names)
|
||||
if(*it == name)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned NameGenerator::getNameCount() {
|
||||
return names.size();
|
||||
}
|
||||
|
||||
void NameGenerator::addName(const std::string& name) {
|
||||
u8it it(name);
|
||||
|
||||
//Add start of word
|
||||
std::pair<int,int> syl;
|
||||
syl.first = it++;
|
||||
if(!syl.first)
|
||||
return;
|
||||
syl.second = it++;
|
||||
if(!syl.second)
|
||||
return;
|
||||
int next = it++;
|
||||
if(!next)
|
||||
return;
|
||||
|
||||
names.push_back(name);
|
||||
auto f = nameStarts.find(syl);
|
||||
if(f == nameStarts.end())
|
||||
nameStarts[syl] = 1;
|
||||
else
|
||||
nameStarts[syl]++;
|
||||
|
||||
//Add association for lowercase
|
||||
if(syl.first >= 'A' && syl.first <= 'Z')
|
||||
addAssociation(syl.first-'A'+'a', syl.second, next);
|
||||
|
||||
//Add all the syllables
|
||||
while(next) {
|
||||
addAssociation(syl.first, syl.second, next);
|
||||
|
||||
syl.first = syl.second;
|
||||
syl.second = next;
|
||||
next = it++;
|
||||
}
|
||||
|
||||
addAssociation(syl.first, syl.second, next);
|
||||
}
|
||||
|
||||
void NameGenerator::addAssociation(int first, int second, int next) {
|
||||
std::pair<int,int> syl;
|
||||
syl.first = first;
|
||||
syl.second = second;
|
||||
|
||||
ProbData* pd = 0;
|
||||
auto it = data.find(syl);
|
||||
if(it == data.end()) {
|
||||
ProbData newData;
|
||||
newData.occurances = 0;
|
||||
data.insert(std::pair<std::pair<int,int>,ProbData>(syl, newData));
|
||||
pd = &data[syl];
|
||||
}
|
||||
else {
|
||||
pd = &it->second;
|
||||
}
|
||||
|
||||
pd->occurances += 1;
|
||||
auto f = pd->nextCount.find(next);
|
||||
if(f == pd->nextCount.end())
|
||||
pd->nextCount[next] = 1;
|
||||
else
|
||||
pd->nextCount[next]++;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
//Second order markov chain name generator
|
||||
class NameGenerator {
|
||||
public:
|
||||
struct ProbData {
|
||||
int occurances;
|
||||
std::unordered_map<int, int> nextCount;
|
||||
};
|
||||
|
||||
std::map<std::pair<int,int>,ProbData> data;
|
||||
std::vector<std::string> names;
|
||||
std::map<std::pair<int,int>,int> nameStarts;
|
||||
std::set<std::string> usedNames;
|
||||
float mutationChance;
|
||||
bool useGeneration;
|
||||
bool preventDuplicates;
|
||||
|
||||
NameGenerator();
|
||||
void clear();
|
||||
|
||||
void read(const std::string& filename);
|
||||
void write(const std::string& filename);
|
||||
|
||||
bool hasName(const std::string& name);
|
||||
void addName(const std::string& name);
|
||||
void addAssociation(int first, int second, int next);
|
||||
unsigned getNameCount();
|
||||
|
||||
std::string generate();
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
#include <random>
|
||||
#include "threads.h"
|
||||
#include <time.h>
|
||||
#include "constants.h"
|
||||
#include "vec3.h"
|
||||
#include "vec2.h"
|
||||
#include "main/references.h"
|
||||
#include "os/driver.h"
|
||||
#include "random.h"
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#define GET_MSB(var, x) do{ unsigned long _index_; _BitScanReverse(&_index_, x); var = _index_ + 1; } while(false)
|
||||
#elif defined(__GNUC__)
|
||||
#define GET_MSB(var, x) do { var = (32 - __builtin_clz(x)); } while(false)
|
||||
#else
|
||||
unsigned _get_msb(unsigned v) {
|
||||
unsigned index = 0;
|
||||
while((1 << index) <= v)
|
||||
++index;
|
||||
return index;
|
||||
}
|
||||
#define GET_MSB(var, x) do { var = _get_msb(x); } while(false)
|
||||
#endif
|
||||
|
||||
Threaded(std::mt19937*) engine;
|
||||
|
||||
void seed(unsigned long seed);
|
||||
|
||||
void initRandomizer() {
|
||||
engine = new std::mt19937();
|
||||
seed((unsigned long)time(0) ^ (unsigned long)threads::getThreadID());
|
||||
}
|
||||
|
||||
void freeRandomizer() {
|
||||
delete engine;
|
||||
}
|
||||
|
||||
void seed(unsigned long seed) {
|
||||
engine->seed(seed);
|
||||
}
|
||||
|
||||
unsigned sysRandomi() {
|
||||
unsigned ret = 0;
|
||||
if(!devices.driver->systemRandom((unsigned char*)&ret, 4)) {
|
||||
if(engine)
|
||||
ret = (unsigned)randomi();
|
||||
else {
|
||||
initRandomizer();
|
||||
ret = (unsigned)randomi();
|
||||
freeRandomizer();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
double randomd() {
|
||||
//Choose random numbers until we don't get max
|
||||
unsigned m = engine->max();
|
||||
unsigned r;
|
||||
do {
|
||||
r = (*engine)();
|
||||
} while(r == m);
|
||||
return (double)r / (double)m;
|
||||
}
|
||||
|
||||
double randomd(double min, double max) {
|
||||
return randomd() * (max - min) + min;
|
||||
}
|
||||
|
||||
double normald(double min, double max, int steps) {
|
||||
double sum = 0;
|
||||
|
||||
for(int i = 0; i < steps; ++i)
|
||||
sum += randomd();
|
||||
|
||||
return min + (max-min)*sum/(double)steps;
|
||||
}
|
||||
|
||||
float randomf() {
|
||||
return (float)randomd();
|
||||
}
|
||||
|
||||
float randomf(float min, float max) {
|
||||
return (float)(randomd() * (double)(max - min)) + min;
|
||||
}
|
||||
|
||||
unsigned randomi() {
|
||||
return (*engine)();
|
||||
}
|
||||
|
||||
int randomi(int min, int max) {
|
||||
unsigned range = (unsigned)max - (unsigned)min;
|
||||
if(range == 0)
|
||||
return min;
|
||||
|
||||
unsigned msb;
|
||||
GET_MSB(msb, range);
|
||||
|
||||
unsigned mask = 0xffffffff >> (32 - msb);
|
||||
|
||||
//Choose uniformly distributed values until one falls into our range
|
||||
//Worst case scenario is the possible values is split in half (+1), so it will still resolve quickly
|
||||
unsigned r;
|
||||
do {
|
||||
r = (unsigned)((*engine)());
|
||||
} while((r & mask) > range);
|
||||
|
||||
return min + (r & mask);
|
||||
}
|
||||
|
||||
vec3d random3d(double radius) {
|
||||
double theta = randomd(0, twopi);
|
||||
|
||||
double u = randomd(-1.0, 1.0);
|
||||
double s = sqrt(1.0-(u*u));
|
||||
|
||||
vec3d out;
|
||||
out.x = s * cos(theta) * radius;
|
||||
out.y = s * sin(theta) * radius;
|
||||
out.z = u * radius;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
vec3d random3d(double minRadius, double maxRadius) {
|
||||
return random3d(minRadius + (maxRadius - minRadius) * sqrt(randomd()));
|
||||
}
|
||||
|
||||
vec2d random2d(double radius) {
|
||||
double theta = randomd(0, twopi);
|
||||
return vec2d(radius * cos(theta), radius * sin(theta));
|
||||
}
|
||||
|
||||
vec2d random2d(double minRadius, double maxRadius) {
|
||||
return random2d(minRadius + (maxRadius - minRadius) * sqrt(randomd()));
|
||||
}
|
||||
|
||||
class MersenneEngine : public RandomEngine {
|
||||
std::mt19937 rnd;
|
||||
public:
|
||||
void seed(unsigned initial) {
|
||||
rnd.seed((unsigned long)initial);
|
||||
}
|
||||
|
||||
unsigned randomi() {
|
||||
return rnd();
|
||||
}
|
||||
|
||||
unsigned randomi(unsigned min, unsigned max) {
|
||||
unsigned range = (unsigned)max - (unsigned)min;
|
||||
if(range == 0)
|
||||
return min;
|
||||
|
||||
unsigned msb;
|
||||
GET_MSB(msb, range);
|
||||
|
||||
unsigned mask = 0xffffffff >> (32 - msb);
|
||||
|
||||
//Choose uniformly distributed values until one falls into our range
|
||||
//Worst case scenario is the possible values is split in half (+1), so it will still resolve quickly
|
||||
unsigned r;
|
||||
do {
|
||||
r = (unsigned)(rnd());
|
||||
} while((r & mask) > range);
|
||||
|
||||
return min + (r & mask);
|
||||
}
|
||||
|
||||
double randomd() {
|
||||
unsigned m = rnd.max();
|
||||
unsigned r;
|
||||
do {
|
||||
r = rnd();
|
||||
} while(r == m);
|
||||
return (double)r / (double)m;
|
||||
}
|
||||
|
||||
double randomd(double min, double max) {
|
||||
return (randomd() * (max - min)) + min;
|
||||
}
|
||||
};
|
||||
|
||||
RandomEngine* RandomEngine::makeMersenne(unsigned seed) {
|
||||
auto* engine = new MersenneEngine();
|
||||
engine->seed(seed);
|
||||
return engine;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "vec2.h"
|
||||
|
||||
// Generates random numbers with a thread-local generator
|
||||
|
||||
void initRandomizer();
|
||||
void freeRandomizer();
|
||||
|
||||
void seed(unsigned long seed);
|
||||
|
||||
//Returns a high-quality random integer (slow)
|
||||
unsigned sysRandomi();
|
||||
|
||||
//Random double in (0.0, 1.0)
|
||||
double randomd();
|
||||
//Random double in (min, max)
|
||||
double randomd(double min, double max);
|
||||
//Pseudo-normal distribution within min,max
|
||||
double normald(double min, double max, int steps = 4);
|
||||
|
||||
//Random float in (0.0, 1.0)
|
||||
float randomf();
|
||||
//Random float in (min, max)
|
||||
float randomf(float min, float max);
|
||||
|
||||
//Random int in [0, INT_MAX]
|
||||
unsigned randomi();
|
||||
//Random int in [min, max]
|
||||
int randomi(int min, int max);
|
||||
|
||||
//Random on sphere surface
|
||||
vec3d random3d(double radius = 1.0);
|
||||
//Random on a donut sphere
|
||||
vec3d random3d(double minRadius, double maxRadius);
|
||||
|
||||
//Random on circle circumference
|
||||
vec2d random2d(double radius = 1.0);
|
||||
//Random on a donut circle
|
||||
vec2d random2d(double minRadius, double maxRadius);
|
||||
|
||||
|
||||
class RandomEngine {
|
||||
public:
|
||||
virtual void seed(unsigned initial) = 0;
|
||||
virtual unsigned randomi() = 0;
|
||||
virtual unsigned randomi(unsigned min, unsigned max) = 0;
|
||||
virtual double randomd() = 0;
|
||||
virtual double randomd(double min, double max) = 0;
|
||||
|
||||
static RandomEngine* makeMersenne(unsigned seed);
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
#include "threads.h"
|
||||
|
||||
template <class T>
|
||||
class _RefCounted {
|
||||
public:
|
||||
mutable T refs;
|
||||
_RefCounted() : refs(1) {}
|
||||
void grab() const { ++refs; }
|
||||
void drop() const { if(!--refs) delete this; }
|
||||
virtual ~_RefCounted() {}
|
||||
};
|
||||
|
||||
typedef _RefCounted<int> RefCounted;
|
||||
typedef _RefCounted<threads::atomic_int> AtomicRefCounted;
|
||||
|
||||
template<class _T>
|
||||
struct heldPointer {
|
||||
//Actual Pointer - do not directly alter without handling the grab/drop yourself
|
||||
_T* ptr;
|
||||
|
||||
//Handles all grabbing and dropping when a pointer changes value
|
||||
inline void operator= (_T* newPtr) {
|
||||
//Grabs the new first, incase new == old
|
||||
//We don't explicitly check, because that's a relatively unlikely case, and only adds overhead where not necessary
|
||||
if(newPtr)
|
||||
newPtr->grab();
|
||||
|
||||
if(ptr)
|
||||
ptr->drop();
|
||||
|
||||
ptr = newPtr;
|
||||
}
|
||||
|
||||
inline void operator= (const heldPointer<_T>& other) {
|
||||
//Grabs the new first, incase new == old
|
||||
//We don't explicitly check, because that's a relatively unlikely case, and only adds overhead where not necessary
|
||||
if(other.ptr)
|
||||
other.ptr->grab();
|
||||
|
||||
if(ptr)
|
||||
ptr->drop();
|
||||
|
||||
ptr = other.ptr;
|
||||
}
|
||||
|
||||
inline void operator= (heldPointer<_T>&& other) {
|
||||
if(ptr)
|
||||
ptr->drop();
|
||||
ptr = other.ptr;
|
||||
other.ptr = 0;
|
||||
}
|
||||
|
||||
inline bool operator< (const heldPointer<_T>& other) {
|
||||
return ptr < other.ptr;
|
||||
}
|
||||
|
||||
//Only drop()s the previous pointer, does not grab the new pointer
|
||||
inline void set(_T* newPtr) {
|
||||
if(ptr)
|
||||
ptr->drop();
|
||||
|
||||
ptr = newPtr;
|
||||
}
|
||||
|
||||
//Clears the value of the pointer, not handling reference counting
|
||||
inline void reset() {
|
||||
ptr = 0;
|
||||
}
|
||||
|
||||
inline _T* operator-> () {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline const _T* operator-> () const {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline operator _T*() {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline operator const _T*() const {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline void swap(heldPointer<_T>& other) {
|
||||
_T* swap_ptr = ptr;
|
||||
ptr = other.ptr;
|
||||
other.ptr = swap_ptr;
|
||||
}
|
||||
|
||||
//Checks if the object pointed to is valid ( via ->isValid() )
|
||||
//If it isn't drop the object, and return false
|
||||
bool validate() {
|
||||
if(ptr) {
|
||||
if(ptr->isValid())
|
||||
return true;
|
||||
ptr->drop();
|
||||
ptr = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
heldPointer() : ptr(0) {}
|
||||
|
||||
heldPointer(_T& start) : ptr(&start) {
|
||||
ptr->grab();
|
||||
}
|
||||
|
||||
heldPointer(_T* start) : ptr(start) {
|
||||
if(ptr)
|
||||
ptr->grab();
|
||||
}
|
||||
|
||||
heldPointer(const heldPointer<_T>& copy) : ptr(copy.ptr) {
|
||||
if(ptr)
|
||||
ptr->grab();
|
||||
}
|
||||
|
||||
heldPointer(heldPointer<_T>&& move) : ptr(move.ptr) {
|
||||
move.ptr = 0;
|
||||
}
|
||||
|
||||
~heldPointer() {
|
||||
if(ptr)
|
||||
ptr->drop();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,355 @@
|
||||
#include "save_file.h"
|
||||
#ifdef _MSC_VER
|
||||
#include "zlib/zlib.h"
|
||||
#else
|
||||
#include "zlib.h"
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include "compat/misc.h"
|
||||
#include "obj/object.h"
|
||||
#include <unordered_map>
|
||||
#include "files.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/game_platform.h"
|
||||
#include "main/references.h"
|
||||
#include "scripts/context_cache.h"
|
||||
|
||||
const char* saveIdentifier = "$SR2";
|
||||
const unsigned gzBufferSize = 128000;
|
||||
|
||||
class SaveFileWriter : public SaveFile {
|
||||
unsigned boundaryID;
|
||||
gzFile out;
|
||||
const std::string destName, tempName;
|
||||
|
||||
std::vector<std::unordered_map<std::string,int>> identifiers;
|
||||
|
||||
bool addIdentifier(unsigned type, int id, const std::string& ident) {
|
||||
if(type >= identifiers.size()) {
|
||||
identifiers.resize(type+1);
|
||||
}
|
||||
else {
|
||||
auto it = identifiers[type].find(ident);
|
||||
if(it != identifiers[type].end())
|
||||
return false;
|
||||
}
|
||||
|
||||
identifiers[type][ident] = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void saveIdentifiers() {
|
||||
unsigned cnt = identifiers.size();
|
||||
*this << cnt;
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
auto& mp = identifiers[i];
|
||||
|
||||
unsigned icnt = mp.size();
|
||||
*this << icnt;
|
||||
foreach(it, mp) {
|
||||
*this << it->first;
|
||||
*this << it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeIdentifier(unsigned type, int id) {
|
||||
*this << id;
|
||||
}
|
||||
|
||||
void write(const void* source, unsigned bytes) {
|
||||
if(bytes != 0)
|
||||
gzwrite(out,source,bytes);
|
||||
}
|
||||
|
||||
void close() {
|
||||
std::string destTempName = destName + ".temp";
|
||||
|
||||
gzclose(out); out = 0;
|
||||
|
||||
//Move the existing save file to a .temp version
|
||||
int oldMoved = rename(destName.c_str(), destTempName.c_str());
|
||||
//Move our temp file to the intended destination
|
||||
int result = rename(tempName.c_str(), destName.c_str());
|
||||
|
||||
if(result == 0) {
|
||||
//Saving succeeded, remove the old save file
|
||||
remove(destTempName.c_str());
|
||||
|
||||
if(devices.cloud)
|
||||
devices.cloud->writeCloudFile(destName, std::string("saves/") + getBasename(destName));
|
||||
|
||||
delete this;
|
||||
}
|
||||
else {
|
||||
//Saving failed, remove the temp file and replace the old save file
|
||||
remove(tempName.c_str());
|
||||
if(oldMoved == 0)
|
||||
rename(destTempName.c_str(), destName.c_str());
|
||||
delete this;
|
||||
throw SaveFileError("Could not save file");
|
||||
}
|
||||
}
|
||||
|
||||
void read(void* dest, unsigned bytes) {
|
||||
throw SaveFileError("Cannot read while writing");
|
||||
}
|
||||
|
||||
void boundary() {
|
||||
*this << boundaryID++;
|
||||
}
|
||||
|
||||
public:
|
||||
SaveFileWriter(const std::string& file) : boundaryID(100), destName(file), tempName(getTemporaryFile()) {
|
||||
out = gzopen(tempName.c_str(),"wb1f");
|
||||
scriptVersion = 0;
|
||||
startVersion = 0;
|
||||
|
||||
if(out == 0)
|
||||
throw SaveFileError("Could not open temporary file");
|
||||
|
||||
gzbuffer(out, gzBufferSize);
|
||||
write(saveIdentifier, 4);
|
||||
|
||||
*this << SFV_Current;
|
||||
}
|
||||
};
|
||||
|
||||
void readSaveFileInfo(SaveFile& file, SaveFileInfo& info) {
|
||||
file >> info.version;
|
||||
if(file >= SFV_0005)
|
||||
file >> info.startVersion;
|
||||
else
|
||||
info.startVersion = info.version;
|
||||
|
||||
unsigned cnt = 0;
|
||||
file >> cnt;
|
||||
info.mods.resize(cnt);
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
file >> info.mods[i].id;
|
||||
if(file >= SFV_0011)
|
||||
file >> info.mods[i].version;
|
||||
else
|
||||
info.mods[i].version = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool getSaveFileInfo(const std::string& fname, SaveFileInfo& info) {
|
||||
try {
|
||||
auto* pfile = SaveFile::open(fname, SM_Read);
|
||||
if(pfile == nullptr)
|
||||
return false;
|
||||
pfile->loadIdentifiers();
|
||||
readSaveFileInfo(*pfile, info);
|
||||
pfile->close();
|
||||
return true;
|
||||
}
|
||||
catch(SaveFileError& err) {
|
||||
error("Failed to read save '%s':\n %s", fname.c_str(), err.text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class SaveFileReader : public SaveFile {
|
||||
unsigned boundaryID;
|
||||
gzFile in;
|
||||
|
||||
std::vector<std::unordered_map<std::string,int>> identifiers;
|
||||
std::vector<std::unordered_map<int,int>> identMap;
|
||||
std::vector<std::unordered_map<std::string,int>> loaded;
|
||||
|
||||
bool addIdentifier(unsigned type, int id, const std::string& ident) {
|
||||
if(type >= identifiers.size()) {
|
||||
identifiers.resize(type+1);
|
||||
}
|
||||
else {
|
||||
auto it = identifiers[type].find(ident);
|
||||
if(it != identifiers[type].end())
|
||||
return false;
|
||||
}
|
||||
|
||||
identifiers[type][ident] = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void addDummyLoadIdentifier(unsigned type, int id, const std::string& ident) {
|
||||
if(type >= loaded.size())
|
||||
loaded.resize(type+1);
|
||||
loaded[type][ident] = id;
|
||||
}
|
||||
|
||||
void loadIdentifiers() {
|
||||
unsigned cnt = *this;
|
||||
loaded.resize(cnt);
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
auto& table = loaded[i];
|
||||
|
||||
unsigned icnt = *this;
|
||||
std::string ident;
|
||||
int id;
|
||||
|
||||
for(unsigned j = 0; j < icnt; ++j) {
|
||||
*this >> ident;
|
||||
*this >> id;
|
||||
table[ident] = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void finalizeIdentifiers() {
|
||||
unsigned cnt = (unsigned)loaded.size();
|
||||
identMap.resize(cnt);
|
||||
if(cnt > identifiers.size())
|
||||
identifiers.resize(cnt);
|
||||
for(unsigned i = 0; i < cnt; ++i) {
|
||||
auto& mp = identifiers[i];
|
||||
auto& table = identMap[i];
|
||||
|
||||
foreach(it, loaded[i]) {
|
||||
auto ft = mp.find(it->first);
|
||||
if(ft != mp.end())
|
||||
table[it->second] = ft->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned getPrevIdentifierCount(unsigned type) {
|
||||
if(type >= identMap.size())
|
||||
return 0;
|
||||
return identMap[type].size();
|
||||
}
|
||||
|
||||
unsigned getIdentifierCount(unsigned type) {
|
||||
if(type >= identifiers.size())
|
||||
return 0;
|
||||
return identifiers[type].size();
|
||||
}
|
||||
|
||||
int getIdentifier(unsigned type, int id) {
|
||||
if(type >= identMap.size())
|
||||
return -1;
|
||||
auto ft = identMap[type].find(id);
|
||||
if(ft == identMap[type].end())
|
||||
return -1;
|
||||
return ft->second;
|
||||
}
|
||||
|
||||
int readIdentifier(unsigned type) {
|
||||
int id;
|
||||
*this >> id;
|
||||
|
||||
if(type >= identMap.size())
|
||||
return -1;
|
||||
|
||||
auto ft = identMap[type].find(id);
|
||||
if(ft == identMap[type].end())
|
||||
return -1;
|
||||
|
||||
return ft->second;
|
||||
}
|
||||
|
||||
void read(void* dest, unsigned bytes) {
|
||||
if(bytes != 0) {
|
||||
int readBytes = gzread(in, dest, bytes);
|
||||
if(readBytes < int(bytes)) {
|
||||
scripts::logException();
|
||||
throw SaveFileError("Unexpected end of file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write(const void* source, unsigned bytes) {
|
||||
throw SaveFileError("Cannot write while reading");
|
||||
}
|
||||
|
||||
void close() {
|
||||
gzclose(in);
|
||||
delete this;
|
||||
}
|
||||
|
||||
void boundary() {
|
||||
unsigned checkID = *this;
|
||||
if(checkID != boundaryID)
|
||||
throw SaveFileError("Boundary did not match");
|
||||
++boundaryID;
|
||||
}
|
||||
public:
|
||||
SaveFileReader(const std::string& file) : boundaryID(100) {
|
||||
scriptVersion = 0;
|
||||
startVersion = 0;
|
||||
in = gzopen(file.c_str(), "rb");
|
||||
if(in == 0)
|
||||
throw SaveFileError("Could not open save file");
|
||||
|
||||
gzbuffer(in, gzBufferSize);
|
||||
|
||||
char buff[5]; buff[4] = '\0';
|
||||
read(buff, 4);
|
||||
|
||||
if(strcmp(buff, saveIdentifier) != 0)
|
||||
throw SaveFileError("Not a Star Ruler save");
|
||||
|
||||
*this >> version;
|
||||
if(version < SFV_EarliestSupported)
|
||||
throw SaveFileError("Save file version no longer supported");
|
||||
else if(version >= SFV_Future)
|
||||
throw SaveFileError("Save file from a newer version, please update");
|
||||
}
|
||||
};
|
||||
|
||||
SaveFile* SaveFile::open(const std::string& file, SaveMode mode) {
|
||||
if(mode == SM_Read)
|
||||
return new SaveFileReader(file);
|
||||
else if(mode == SM_Write)
|
||||
return new SaveFileWriter(file);
|
||||
else
|
||||
throw SaveFileError("Invalid save file mode");
|
||||
}
|
||||
|
||||
SaveFile& SaveFile::operator<<(const char* str) {
|
||||
size_t len = strlen(str);
|
||||
if(len > 0xffff)
|
||||
throw SaveFileError("String too long");
|
||||
unsigned short shortLen = (unsigned short)len;
|
||||
write(&shortLen,2);
|
||||
write(str,shortLen);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SaveFile& SaveFile::operator<<(const std::string& str) {
|
||||
size_t len = str.size();
|
||||
if(len > 0xffff)
|
||||
throw SaveFileError("String too long");
|
||||
unsigned short shortLen = (unsigned short)len;
|
||||
write(&shortLen,2);
|
||||
if(shortLen > 0)
|
||||
write(str.c_str(),shortLen);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SaveFile& SaveFile::operator>>(std::string& str) {
|
||||
unsigned short length;
|
||||
read(&length,2);
|
||||
if(length > 0) {
|
||||
void* temp = alloca(length);
|
||||
read(temp,length);
|
||||
str.assign((const char*)temp, length);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SaveFile& SaveFile::operator>>(Object*& obj) {
|
||||
obj = getObjectByID(read<int>(), true);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Object* SaveFile::readExistingObject() {
|
||||
return getObjectByID(read<int>());
|
||||
}
|
||||
|
||||
SaveFile& SaveFile::operator<<(const Object* obj) {
|
||||
int id = obj ? obj->id : 0;
|
||||
return *this << id;
|
||||
}
|
||||
|
||||
SaveFile::~SaveFile() {}
|
||||
@@ -0,0 +1,286 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "network/message.h"
|
||||
class Object;
|
||||
|
||||
//SaveFile
|
||||
//
|
||||
//Create with SaveFile::open("file path", SF_Read or SF_Write)
|
||||
//Close with SaveFile.close()
|
||||
//
|
||||
//In write mode, write data via << syntax, e.g.
|
||||
// SaveFile << myVariable;
|
||||
// const char* and std::string may both be written this way
|
||||
//Direct write is available via write(data, bytes)
|
||||
// write(&myVariable, sizeof(myVariable))
|
||||
//
|
||||
//
|
||||
//In read mode, read data via >> syntax, e.g.
|
||||
// SaveFile >> myVariable;
|
||||
// Strings written via << may be read into a std::string this way
|
||||
//Direct read is available via read(data, bytes)
|
||||
// read(&myVariable, sizeof(myVariable)
|
||||
//
|
||||
//To support different version formats, use comparison syntax, e.g.
|
||||
// if(SaveFile > SFV_0005) {}
|
||||
//
|
||||
//Strings may be saved as a std::string or a const char*, but only loaded as std::string
|
||||
//
|
||||
//Errors are thrown as SaveFileError()
|
||||
|
||||
enum SaveMode {
|
||||
SM_Read,
|
||||
SM_Write
|
||||
};
|
||||
|
||||
enum SaveIdentifier {
|
||||
SI_Subsystem,
|
||||
SI_SubsystemVar,
|
||||
SI_HexVar,
|
||||
SI_ShipVar,
|
||||
SI_Hull,
|
||||
SI_Shipset,
|
||||
SI_Effector,
|
||||
SI_Effect,
|
||||
SI_SubsystemModule,
|
||||
SI_SubsystemModifier,
|
||||
|
||||
SI_SCRIPT_START = 32,
|
||||
};
|
||||
|
||||
struct SaveFileError {
|
||||
const char* text;
|
||||
|
||||
SaveFileError(const char* Text) : text(Text) {}
|
||||
};
|
||||
|
||||
struct SavedMod {
|
||||
std::string id;
|
||||
unsigned short version;
|
||||
};
|
||||
|
||||
struct SaveFileInfo {
|
||||
unsigned version;
|
||||
unsigned startVersion;
|
||||
std::vector<SavedMod> mods;
|
||||
};
|
||||
|
||||
enum SaveFileVersion {
|
||||
SFV_0000 = 0,
|
||||
SFV_0001 = 0,
|
||||
SFV_0002,
|
||||
SFV_0003,
|
||||
SFV_0004,
|
||||
SFV_0005,
|
||||
SFV_0006,
|
||||
SFV_0007,
|
||||
SFV_0008,
|
||||
SFV_0009,
|
||||
SFV_0010,
|
||||
SFV_0011,
|
||||
SFV_0012,
|
||||
SFV_0013,
|
||||
SFV_0014,
|
||||
SFV_0015,
|
||||
SFV_0016,
|
||||
SFV_0017,
|
||||
SFV_0018,
|
||||
SFV_0019,
|
||||
SFV_0020,
|
||||
SFV_0021,
|
||||
SFV_0022,
|
||||
|
||||
SFV_Future,
|
||||
SFV_Current = SFV_Future - 1,
|
||||
|
||||
SFV_EarliestSupported = SFV_0000
|
||||
};
|
||||
|
||||
class SaveFile {
|
||||
protected:
|
||||
virtual ~SaveFile();
|
||||
SaveFileVersion version;
|
||||
public:
|
||||
unsigned scriptVersion;
|
||||
unsigned startVersion;
|
||||
|
||||
//Opens a file in either read or write mode
|
||||
//Throws SaveFileError if the file cannot be accessed
|
||||
static SaveFile* open(const std::string& file, SaveMode mode);
|
||||
|
||||
//Closes and deletes the SaveFile
|
||||
//Throws SaveFileError if something didn't succeed, but still deletes the SaveFile
|
||||
virtual void close() = 0;
|
||||
|
||||
//Version checks
|
||||
bool operator>(SaveFileVersion Version) const {
|
||||
return version > Version;
|
||||
}
|
||||
|
||||
bool operator>=(SaveFileVersion Version) const {
|
||||
return version >= Version;
|
||||
}
|
||||
|
||||
bool operator<(SaveFileVersion Version) const {
|
||||
return version < Version;
|
||||
}
|
||||
|
||||
bool operator<=(SaveFileVersion Version) const {
|
||||
return version <= Version;
|
||||
}
|
||||
|
||||
bool operator==(SaveFileVersion Version) const {
|
||||
return version == Version;
|
||||
}
|
||||
|
||||
bool operator!=(SaveFileVersion Version) const {
|
||||
return version != Version;
|
||||
}
|
||||
|
||||
//Helper functions
|
||||
|
||||
//Marks a boundary location; During a load, checks that the boundary is present
|
||||
virtual void boundary() = 0;
|
||||
|
||||
//Write functions
|
||||
virtual void write(const void* source, unsigned bytes) = 0;
|
||||
|
||||
template<class type>
|
||||
SaveFile& operator<<(const type& data) {
|
||||
write(&data, sizeof(type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class type>
|
||||
SaveFile& operator<<(const type* data) {
|
||||
write(data, sizeof(type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
SaveFile& operator<<(const char* str);
|
||||
|
||||
SaveFile& operator<<(char* str) {
|
||||
return *this << (const char*)str;
|
||||
}
|
||||
|
||||
SaveFile& operator<<(const std::string& str);
|
||||
|
||||
SaveFile& operator<<(std::string& str) {
|
||||
return *this << (const std::string&)str;
|
||||
}
|
||||
|
||||
SaveFile& operator<<(const Object* obj);
|
||||
|
||||
SaveFile& operator<<(Object* obj) {
|
||||
return *this << (const Object*)obj;
|
||||
}
|
||||
|
||||
template<class type>
|
||||
SaveFile& writeConditional(bool condition, const type& data) {
|
||||
*this << condition;
|
||||
if(condition)
|
||||
*this << data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//Read function
|
||||
virtual void read(void* dest, unsigned bytes) = 0;
|
||||
|
||||
template<class type>
|
||||
type read() {
|
||||
type temp;
|
||||
read(&temp, sizeof(type));
|
||||
return temp;
|
||||
}
|
||||
|
||||
template<class type>
|
||||
SaveFile& operator>>(type& data) {
|
||||
read(&data, sizeof(type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class type>
|
||||
SaveFile& operator>>(const type* data) {
|
||||
read(data, sizeof(type));
|
||||
return *this;
|
||||
}
|
||||
|
||||
SaveFile& operator>>(std::string& str);
|
||||
|
||||
SaveFile& operator>>(Object*& obj);
|
||||
|
||||
Object* readExistingObject();
|
||||
|
||||
template<class type>
|
||||
bool readConditional(type& data) {
|
||||
bool condition;
|
||||
*this >> condition;
|
||||
if(condition)
|
||||
*this >> data;
|
||||
else
|
||||
data = type();
|
||||
return condition;
|
||||
}
|
||||
|
||||
template<class type>
|
||||
operator type() {
|
||||
type temp;
|
||||
*this >> temp;
|
||||
return temp;
|
||||
}
|
||||
|
||||
//Indentifier tables
|
||||
virtual bool addIdentifier(unsigned type, int id, const std::string& ident) = 0;
|
||||
virtual void addDummyLoadIdentifier(unsigned type, int id, const std::string& ident) {};
|
||||
virtual void saveIdentifiers() {};
|
||||
virtual void loadIdentifiers() {};
|
||||
virtual void finalizeIdentifiers() {};
|
||||
virtual int readIdentifier(unsigned type) { return -1; };
|
||||
virtual int getIdentifier(unsigned type, int id) { return -1; };
|
||||
virtual unsigned getPrevIdentifierCount(unsigned type) { return 0; }
|
||||
virtual unsigned getIdentifierCount(unsigned type) { return 0; }
|
||||
virtual void writeIdentifier(unsigned type, int id) {};
|
||||
};
|
||||
|
||||
void readSaveFileInfo(SaveFile& file, SaveFileInfo& info);
|
||||
bool getSaveFileInfo(const std::string& file, SaveFileInfo& info);
|
||||
|
||||
class SaveMessage : public net::Message {
|
||||
public:
|
||||
SaveFile& file;
|
||||
|
||||
SaveMessage(SaveFile& sav) : net::Message(), file(sav) {
|
||||
}
|
||||
|
||||
SaveMessage(SaveMessage& other) : net::Message(other), file(other.file) {
|
||||
}
|
||||
|
||||
void operator=(const SaveMessage& other) {
|
||||
net::Message::operator=(other);
|
||||
}
|
||||
|
||||
bool operator>(SaveFileVersion Version) const {
|
||||
return file > Version;
|
||||
}
|
||||
|
||||
bool operator>=(SaveFileVersion Version) const {
|
||||
return file >= Version;
|
||||
}
|
||||
|
||||
bool operator<(SaveFileVersion Version) const {
|
||||
return file < Version;
|
||||
}
|
||||
|
||||
bool operator<=(SaveFileVersion Version) const {
|
||||
return file <= Version;
|
||||
}
|
||||
|
||||
bool operator==(SaveFileVersion Version) const {
|
||||
return file == Version;
|
||||
}
|
||||
|
||||
bool operator!=(SaveFileVersion Version) const {
|
||||
return file != Version;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "stat_history.h"
|
||||
|
||||
void StatEntry::addEvent(unsigned short type, const std::string& name) {
|
||||
StatEvent* newEvent = new StatEvent;
|
||||
newEvent->name = name;
|
||||
newEvent->type = type;
|
||||
|
||||
if(!evt) {
|
||||
evt = newEvent;
|
||||
}
|
||||
else {
|
||||
newEvent->next = evt;
|
||||
evt = newEvent;
|
||||
}
|
||||
}
|
||||
|
||||
StatHistory::StatHistory() : head(0), tail(0) {
|
||||
}
|
||||
|
||||
StatEntry* StatHistory::addStatEntry(unsigned time) {
|
||||
if(tail && tail->time >= time) {
|
||||
return tail;
|
||||
}
|
||||
|
||||
StatEntry* entry = new StatEntry;
|
||||
entry->time = time;
|
||||
|
||||
if(tail) {
|
||||
tail->next = entry;
|
||||
entry->prev = tail;
|
||||
tail = entry;
|
||||
}
|
||||
else {
|
||||
head = entry;
|
||||
tail = entry;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
const StatEntry* StatHistory::getHead() const {
|
||||
return head;
|
||||
}
|
||||
|
||||
StatEntry* StatHistory::getTail() const {
|
||||
return tail;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
struct StatEvent {
|
||||
std::string name;
|
||||
StatEvent* next;
|
||||
unsigned short type;
|
||||
|
||||
StatEvent() : type(0), next(0) {}
|
||||
};
|
||||
|
||||
struct StatEntry {
|
||||
StatEntry* next, *prev;
|
||||
StatEvent* evt;
|
||||
unsigned time;
|
||||
|
||||
union {
|
||||
int asInt;
|
||||
float asFloat;
|
||||
};
|
||||
|
||||
void addEvent(unsigned short type, const std::string& name);
|
||||
|
||||
StatEntry() : next(0), prev(0), time(0), evt(0), asInt(0) {}
|
||||
};
|
||||
|
||||
class StatHistory {
|
||||
StatEntry* head, *tail;
|
||||
public:
|
||||
|
||||
StatHistory();
|
||||
|
||||
StatEntry* addStatEntry(unsigned time);
|
||||
const StatEntry* getHead() const;
|
||||
StatEntry* getTail() const;
|
||||
};
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "threaded_loader.h"
|
||||
#include "threads.h"
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
|
||||
namespace Loading {
|
||||
|
||||
struct Task;
|
||||
std::function<void(void)> threadSetup, threadCleanup;
|
||||
|
||||
threads::atomic_int workers(0);
|
||||
bool finalized = false;
|
||||
double startTime;
|
||||
|
||||
threads::Mutex taskLock;
|
||||
threads::Signal processingTasks;
|
||||
bool tasksFinished;
|
||||
std::unordered_map<std::string, Task*> namedTasks;
|
||||
std::list<Task*> tasks;
|
||||
auto nextTask = tasks.end();
|
||||
|
||||
struct Task {
|
||||
std::string name;
|
||||
|
||||
std::vector<Task*> dependencies;
|
||||
|
||||
bool finished;
|
||||
double executionTime;
|
||||
|
||||
int threadRestriction;
|
||||
|
||||
std::function<void(void)> _execute;
|
||||
|
||||
Task(const std::string& Name, const char* depends) : name(Name), finished(false), threadRestriction(-1) {
|
||||
while(depends && depends[0] != '\0') {
|
||||
const char* end = strchr(depends+1, ',');
|
||||
if(end == 0)
|
||||
end = strchr(depends, '\0');
|
||||
|
||||
std::string dependencyName(depends, end - depends);
|
||||
if(dependencyName == name)
|
||||
throw "Cylic loading depedency";
|
||||
|
||||
auto dependency = namedTasks.find(dependencyName);
|
||||
if(dependency != namedTasks.end())
|
||||
dependencies.push_back(dependency->second);
|
||||
else
|
||||
throw "Missing dependency";
|
||||
|
||||
depends = *end == ',' ? end+1 : end;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
if(namedTasks.find(Name) != namedTasks.end())
|
||||
throw "Duplicate task";
|
||||
#endif
|
||||
|
||||
namedTasks[Name] = this;
|
||||
}
|
||||
|
||||
bool mayExecute() {
|
||||
if(threadRestriction != threads::invalidThreadID && threadRestriction != threads::getThreadID())
|
||||
return false;
|
||||
for(unsigned i = 0; i < dependencies.size(); ++i)
|
||||
if(!dependencies[i]->isFinished())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isMyJob() {
|
||||
return threadRestriction == threads::getThreadID();
|
||||
}
|
||||
|
||||
void execute() {
|
||||
if(isFinished())
|
||||
return;
|
||||
double start = devices.driver->getAccurateTime();
|
||||
info("%s started at %.1fs on thread %i", name.c_str(), start - startTime, threads::getThreadID());
|
||||
|
||||
_execute();
|
||||
double end = devices.driver->getAccurateTime();
|
||||
executionTime = end - start;
|
||||
finished = true;
|
||||
|
||||
info("%s took %.1fms on thread %i", name.c_str(), executionTime * 1000.0, threads::getThreadID());
|
||||
}
|
||||
|
||||
bool isFinished() {
|
||||
return finished;
|
||||
}
|
||||
};
|
||||
|
||||
void addTask(const std::string& name, const char* depends, std::function<void(void)> execute, int threadRestriction) {
|
||||
Task* task = new Task(name, depends);
|
||||
|
||||
task->_execute = execute;
|
||||
task->threadRestriction = threadRestriction;
|
||||
|
||||
taskLock.lock();
|
||||
tasks.push_back(task);
|
||||
taskLock.release();
|
||||
}
|
||||
|
||||
bool finished() {
|
||||
return tasksFinished;
|
||||
}
|
||||
|
||||
threads::threadreturn threadcall processLoad(void* arg) {
|
||||
if(threadSetup)
|
||||
threadSetup();
|
||||
|
||||
while(!finalized || !tasks.empty()) {
|
||||
process();
|
||||
threads::sleep(1);
|
||||
}
|
||||
--workers;
|
||||
|
||||
//This lets us have accurate timings, since we want
|
||||
//to know when the last task finished, not when
|
||||
//the loading period is over (ie for preloading)
|
||||
if(workers == 0) {
|
||||
processingTasks.wait(0);
|
||||
double totalTime = 0;
|
||||
auto iTask = namedTasks.begin();
|
||||
while(iTask != namedTasks.end()) {
|
||||
totalTime += iTask->second->executionTime;
|
||||
|
||||
delete iTask->second;
|
||||
iTask = namedTasks.erase(iTask);
|
||||
}
|
||||
|
||||
double time = devices.driver->getAccurateTime(), loadTime = time - startTime;
|
||||
print("Loaded in %.1f seconds", loadTime);
|
||||
info("Tasks used a total of %.1f seconds (%d%% faster)", totalTime, (int)(100.0*totalTime/loadTime)-100);
|
||||
tasksFinished = true;
|
||||
}
|
||||
|
||||
if(threadCleanup)
|
||||
threadCleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void prepare(unsigned threads, std::function<void(void)> threadPrep, std::function<void(void)> threadExit) {
|
||||
threadSetup = threadPrep;
|
||||
threadCleanup = threadExit;
|
||||
tasksFinished = false;
|
||||
|
||||
startTime = devices.driver->getAccurateTime();
|
||||
workers = threads;
|
||||
for(unsigned i = 0; i < threads; ++i)
|
||||
threads::createThread(processLoad,0);
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
double time = devices.driver->getAccurateTime();
|
||||
info("Preparing tasks took %.1f ms", (time - startTime) * 1000.0);
|
||||
|
||||
finalized = true;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
while(workers != 0)
|
||||
threads::sleep(0);
|
||||
finalized = false;
|
||||
|
||||
nextTask = tasks.end();
|
||||
}
|
||||
|
||||
void process() {
|
||||
processingTasks.signalUp();
|
||||
if(tasks.empty()) {
|
||||
processingTasks.signalDown();
|
||||
return;
|
||||
}
|
||||
|
||||
taskLock.lock();
|
||||
|
||||
Task* task = 0;
|
||||
for(auto i = tasks.begin(), end = tasks.end(); i != end; ++i) {
|
||||
Task* check = *i;
|
||||
if(check->isMyJob() && check->mayExecute()) {
|
||||
task = check;
|
||||
if(nextTask == i)
|
||||
nextTask = tasks.erase(i);
|
||||
else
|
||||
tasks.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(task == 0) {
|
||||
while(tasks.empty() == false) {
|
||||
if(nextTask == tasks.end())
|
||||
nextTask = tasks.begin();
|
||||
|
||||
Task* check = *nextTask;
|
||||
if(check->mayExecute()) {
|
||||
nextTask = tasks.erase(nextTask);
|
||||
task = check;
|
||||
break;
|
||||
}
|
||||
|
||||
++nextTask;
|
||||
taskLock.release();
|
||||
threads::sleep(0);
|
||||
taskLock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
taskLock.release();
|
||||
|
||||
if(task) {
|
||||
task->execute();
|
||||
if(!task->isFinished()) {
|
||||
taskLock.lock();
|
||||
tasks.push_back(task);
|
||||
taskLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
processingTasks.signalDown();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace Loading {
|
||||
|
||||
void prepare(unsigned threads, std::function<void(void)> threadPrep, std::function<void(void)> threadExit);
|
||||
void finalize();
|
||||
void finish();
|
||||
bool finished();
|
||||
|
||||
void addTask(const std::string& name, const char* depends, std::function<void(void)> execute, int threadRestriction = 0);
|
||||
void process();
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user