Compare commits

...

No commits in common. "node" and "main" have entirely different histories.
node ... main

32 changed files with 1024 additions and 5670 deletions

148
.clang-format Normal file
View File

@ -0,0 +1,148 @@
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignArrayOfStructures: Left
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros: []
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeComma
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true
ColumnLimit: 99
QualifierAlignment: Left
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: Always
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: NextLine
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: false
FixNamespaceComments: true
ForEachMacros: []
IfMacros: []
IncludeBlocks: Merge
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: false
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: Wrapped
InsertBraces: true
InsertNewlineAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterOverloadedOperator: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Cpp11
TabWidth: 4
UseCRLF: false
UseTab: Always

18
.gitignore vendored
View File

@ -1,10 +1,14 @@
# Editor
# Executables
*.exe
*.out
*.run
# Editors
*.swp
.vscode/
# node.js & npm
node_modules
# build files
dist/
.parcel-cache/
# Build
.xmake/
build/
*.ilk
*.pdb

BIN
assets/basic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="61.699933" y="30" width="25" height="40" transform="translate(124.199933 -24.199933) rotate(90)" style="fill: #bdbdbd; stroke: #757575; stroke-linejoin: round; stroke-width: 3px;"/>
<circle cx="50" cy="50" r="25" style="fill: #29b6f6; stroke: #757575; stroke-miterlimit: 10; stroke-width: 3px;"/>
</svg>

Before

Width:  |  Height:  |  Size: 447 B

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>openarras</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<canvas width="1000" height="700" id="board"></canvas>
<p id="raw-info"></p>
<script src="./index.js" type="module"></script>
</body>
</html>

View File

@ -1,30 +0,0 @@
import { io } from "socket.io-client";
const socket = io();
function loadImg(url) {
const img_url = url;
const img = new Image();
img.src = img_url;
return new Promise((resolve, reject) => {
img.onload = () => { resolve(img); };
img.onerror = (err) => { reject(err); };
});
}
const imgs = {};
async function loadAssets() {
imgs.basic = await loadImg(new URL('assets/basic.svg', import.meta.url));
}
await loadAssets();
const cvs = document.querySelector("#board");
const ctx = cvs.getContext("2d");
socket.on("update",(tank_info) => {
document.querySelector("#raw-info").innerHTML = JSON.stringify(tank_info);
ctx.clearRect(0, 0, cvs.width, cvs.height);
ctx.drawImage(imgs.basic, tank_info.position.x, tank_info.position.y, 100, 100);
});

137
include/2d.h Normal file
View File

@ -0,0 +1,137 @@
#ifndef header_2d
#define header_2d
#include <array>
#include <complex>
#include <initializer_list>
namespace arras {
using Float = float;
using Vec2 = std::complex<Float>;
const Float eps = 1e-6;
inline Float cross(const Vec2& a, const Vec2& b) {
return a.real() * b.imag() - a.imag() * b.real();
}
inline Float dot(const Vec2& a, const Vec2& b) {
return a.real() * b.real() + a.imag() * b.imag();
}
inline Float asAngle(const Vec2& d) {
return std::atan2(d.imag(), d.real());
}
struct Circle {
Vec2 o;
Float r;
Circle() = delete;
Circle(const Vec2& o, Float r): o(o), r(r) {}
bool contain(const Vec2& a) const {
return abs(o - a) <= r;
}
};
template<int n>
struct Polygon {
static_assert(n >= 3, "Polygon must have at least 3 edges");
std::array<Vec2, n> vertex;
Polygon() = delete;
Polygon(const std::array<Vec2, n>& v): vertex(v) {}
Polygon(std::initializer_list<Vec2>&& v) {
int i = 0;
for (auto&& x : v) {
if (i >= n) {
break;
}
vertex[i] = x;
i += 1;
}
}
Vec2& operator[](int i) {
return vertex[i];
}
Vec2 operator[](int i) const {
return vertex[i];
}
bool contain(const Vec2& a) const {
Float s = 0;
for (int i = 0; i < n; i++) {
Vec2 u = vertex[i] - a, w = vertex[(i + 1) % n] - a;
s += atan2(cross(u, w), dot(u, w));
}
return abs(s) > eps;
}
};
inline bool intersect(const Circle& a, const Circle& b) {
return abs(a.o - b.o) <= a.r + b.r;
}
template<int n, int m>
inline bool intersect(const Polygon<n>& a, const Polygon<m>& b) {
for (int i = 0; i < n; i++) {
if (b.contain(a[i])) {
return true;
}
}
for (int i = 0; i < m; i++) {
if (a.contain(b[i])) {
return true;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (cross(b[j] - a[i], a[(i + 1) % n] - a[i])
* cross(b[(j + 1) % m] - a[i], a[(i + 1) % n] - a[i])
<= 0
&& cross(a[i] - b[j], b[(j + 1) % m] - b[j])
* cross(a[(i + 1) % n] - b[j], b[(j + 1) % m] - b[j])
<= 0) {
return true;
}
}
}
return false;
}
template<int n>
inline bool intersect(const Polygon<n>& a, const Circle& b) {
if (a.contain(b.o)) {
return true;
}
for (int i = 0; i < n; i++) {
if (b.contain(a[i])) {
return true;
}
}
for (int i = 0; i < n; i++) {
Vec2 u = b.o - a[i], v = a[(i + 1) % n] - a[i];
Float c = dot(u, v) / norm(v);
if (0 <= c && c <= 1 && abs(u - c * v) <= b.r) {
return true;
}
return false;
}
}
template<int n>
inline bool intersect(const Circle& b, const Polygon<n>& a) {
return intersect(a, b);
}
}
#endif

98
include/render.h Normal file
View File

@ -0,0 +1,98 @@
#ifndef HEADER_ARRAS_RENDER_H
#define HEADER_ARRAS_RENDER_H
extern "C" {
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
}
#include "2d.h"
#include <fmt/core.h>
#include <memory>
#include <stdexcept>
#include <utility>
namespace arras {
struct Color {
uint8_t r, g, b, a;
Color();
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
};
std::shared_ptr<SDL_Rect> makeRect(int x, int y, int w, int h);
std::shared_ptr<SDL_FRect> makeFRect(const arras::Vec2& pos, const arras::Vec2& size);
class RenderSurface {
public:
RenderSurface(RenderSurface&& rs);
RenderSurface(const RenderSurface& rs);
explicit RenderSurface(SDL_RWops* src);
RenderSurface(int width, int height);
~RenderSurface();
SDL_Surface* nativeHandle() const noexcept;
int width() const noexcept;
int height() const noexcept;
private:
SDL_Surface* s;
};
class RenderWindow;
class RenderTexture {
RenderTexture(const RenderTexture&) = delete;
RenderTexture& operator=(const RenderTexture&) = delete;
public:
RenderTexture(RenderTexture&& rt);
RenderTexture(const RenderWindow& rd, SDL_RWops* src);
RenderTexture(const RenderWindow& rd, const RenderSurface& rs);
~RenderTexture();
SDL_Texture* nativeHandle() const noexcept;
int width() const noexcept;
int height() const noexcept;
std::pair<int, int> calcRenderSize(int w, int h) const noexcept;
private:
SDL_Texture* t;
int w, h;
};
class RenderWindow {
RenderWindow(const RenderWindow&) = delete;
RenderWindow& operator=(const RenderWindow&) = delete;
public:
RenderWindow(RenderWindow&& rd);
RenderWindow(int w, int h);
~RenderWindow();
SDL_Renderer* nativeHandle() const noexcept;
void renderTextureAt(const RenderTexture& t, const Vec2& pos) const;
void renderTextureAt(const RenderTexture& t, const Vec2& pos, const Vec2& direction) const;
void present() const noexcept;
void clear(const Color& c = {}) const;
int width() const noexcept;
int height() const noexcept;
private:
int w, h;
SDL_Window* window;
SDL_Renderer* renderer;
};
} // namespace arras
#endif

32
main.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "2d.h"
#include "render.h"
#include <chrono>
#include <thread>
#include <cmath>
int main() {
arras::RenderWindow w(2360, 1240);
arras::RenderTexture t(w, SDL_RWFromFile("assets/basic.png", "rb"));
std::printf("%d %d\n", t.width(), t.height());
const float PI = acos(-1);
arras::Vec2 d{1, 0}, v{std::cos(PI / 300), std::sin(PI / 300)};
SDL_Event evt;
while (true) {
while (SDL_PollEvent(&evt)) {
if (evt.type == SDL_QUIT) {
break;
}
}
w.clear();
w.renderTextureAt(t, {200, 200}, d);
w.present();
d *= v;
// pos += v;
// if (pos.real() > 1000 || pos.real() < 200) {
// v *= -1;
// }
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
}

32
objects.hpp Normal file
View File

@ -0,0 +1,32 @@
#ifndef HEADER_OBJECTS_HPP
#define HEADER_OBJECTS_HPP
#include "2d.h"
#include <entt/entt.hpp>
namespace arras {
struct Transform {
Vec2 position;
};
struct Velocity {
Vec2 velocity;
};
struct EngineAcceleration {
Vec2 ideal_velocity;
Float acceleration;
};
inline entt::entity makeTank(entt::registry &r, Vec2 position) {
const auto e = r.create();
r.emplace<Transform>(e, Transform{position});
r.emplace<Velocity>(e, Velocity{{0, 0}});
r.emplace<EngineAcceleration>(e, EngineAcceleration{{0, 0}, 1});
return e;
}
}
#endif

4930
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
{
"name": "openarras",
"version": "0.1.0",
"description": "",
"source": "client/index.html",
"entry": "src/main.js",
"scripts": {
"test": "mocha tests/"
},
"repository": {
"type": "git",
"url": "https://git.gzezfisher.top/szTom/openarras.git"
},
"author": "szdytom <szdytom@163.com>",
"license": "Apache-2.0",
"type": "module",
"devDependencies": {
"buffer": "^6.0.3",
"mocha": "^10.2.0",
"parcel": "^2.9.3"
},
"dependencies": {
"express": "^4.18.2",
"socket.io": "^4.7.1",
"socket.io-client": "^4.7.1"
}
}

123
src/2d.js
View File

@ -1,123 +0,0 @@
function sqr(x) {
return x * x;
}
export class Circle {
constructor(o, r) {
this.o = o;
this.r = r;
}
clone() {
return new Circle(this.o.clone(), this.r);
}
equal(c) {
return this.o.equal(c.o) && this.r == c.r;
}
static intersect(a, b) {
return a.o.sub(b.o).norm() <= sqr(a.r + b.r);
}
static collisionElimation(a, b) {
if (!Circle.intersect(a, b)) { return false; }
const d = a.o.distanceTo(b.o);
const ad = (sqr(a.r) - sqr(b.r) + sqr(d)) / (2 * d);
const bd = d - ad;
const v = b.o.sub(a.o);
a.o.subTo(v.adjust(a.r - ad));
b.o.addTo(v.adjust(b.r - bd));
return true;
}
};
export class Vec2 {
constructor(x, y) {
this.x = x || 0;
this.y = y || 0;
}
adjust(len) {
return this.mul(len / this.abs());
}
distanceTo(b) {
return this.sub(b).abs();
}
add(y) {
return new Vec2(this.x + y.x, this.y + y.y);
}
sub(y) {
return new Vec2(this.x - y.x, this.y - y.y);
}
mul(k) {
return new Vec2(this.x * k, this.y * k);
}
conj() {
return new Vec2(this.x, -this.y);
}
yx() {
return new Vec2(this.y, this.x);
}
complexMul(y) {
return new Vec2(this.x * y.x - this.y * y.y, this.x * y.y + this.y * y.x);
}
complexInv() {
return this.conj().mul(1 / this.norm());
}
complexDiv(y) {
return this.complexMul(y.complexInv());
}
addTo(y) {
this.x += y.x;
this.y += y.y;
}
subTo(y) {
this.x -= y.x;
this.y -= y.y;
}
mulTo(k) {
this.x *= k;
this.y *= k;
}
equal(y) {
return this.x == y.x && this.y == y.y;
}
arg() {
return Math.atan2(this.y, this.x);
}
abs() {
return Math.sqrt(this.norm());
}
norm() {
return sqr(this.x) + sqr(this.y);
}
clone() {
return new Vec2(this.x, this.y);
}
dot(y) {
return this.x * y.x + this.y * y.y;
}
cross(y) {
return this.x * y.y - this.y * y.x;
}
};

View File

@ -1,18 +0,0 @@
import { Circle } from '../src/2d.js';
export class Motion {
constructor(p, v) {
this.position = p;
this.velocity = v;
}
};
export class CollisionBox {
constructor(r) {
this.r = r;
}
shape(o) {
return new Circle(o, this.r);
}
};

View File

@ -1,124 +0,0 @@
import { TreapSet } from "./misc/treap.js";
export class EntityRegistry {
constructor() {
this.free_ids = new TreapSet();
this.signatures = [];
this.components = [];
this.component_map = new Map();
}
requireComponentList(cname) {
if (!this.component_map.has(cname)) {
this.component_map.set(cname, this.components.length);
this.components.push([]);
}
return this.components[this.component_map.get(cname)];
}
componentListLowerBound(cl, entity) {
if (cl.length === 0) {
return 0;
} else if (cl[cl.length - 1].__owner_entity < entity) {
return cl.length;
}
let l = 0, r = cl.length - 1, p = 0;
while (l <= r) {
let mid = (l + r) >>> 1;
if (cl[mid].__owner_entity < entity) {
l = mid + 1;
} else {
p = mid;
r = mid - 1;
}
}
return p;
}
assign(entity, cv) {
const cname = cv.constructor.name;
const cl = this.requireComponentList(cname);
cv.__owner_entity = entity;
const p = this.componentListLowerBound(cl, entity);
if (cl[p]?.__owner_entity === entity) {
cl[p] = cv;
return this;
}
cl.splice(p, 0, cv);
this.signatures[entity][this.component_map.get(cname)] = p;
return this;
}
create() {
let id = this.free_ids.takeInstance();
if (id == null) {
id = this.signatures.length;
} else {
this.free_ids.erase(id);
}
this.signatures[id] = [];
return id;
}
destory(entity) {
for (let i in this.signatures[entity]) {
const p = this.signatures[entity][i];
this.components[i].splice(p, 1);
}
this.signatures[entity] = undefined;
this.free_ids.insertRaw(entity);
return this;
}
getRaw(entity, cid) {
return this.components[cid][this.signatures[entity][cid]];
}
get(entity, ctype) {
if (!this.component_map.has(ctype.name)) {
return null;
}
const cid = this.component_map.get(ctype.name);
if (this.signatures[entity][cid] == null) {
return null;
}
return this.getRaw(entity, cid);
}
forEach(rc, func) {
if (rc.length === 0) {
for (let i = 0; i < this.signatures.length; ++i) {
if (this.signatures[i]) {
func.call(this, i);
}
}
return this;
}
const rcid = rc.map(ctype => this.component_map.get(ctype.name));
let p, v = Infinity;
for (let cid of rcid) {
const cl = this.components[cid];
if (cl.length < v) {
v = cl.length;
p = cid;
}
}
for (let c of this.components[p]) {
const e = c.__owner_entity;
if (rcid.every(cid => this.signatures[e][cid] != null)) {
func.apply(this, [e].concat(rcid.map(cid => this.getRaw(e, cid))));
}
}
return this;
}
};

View File

@ -1,67 +0,0 @@
import { Vec2 } from '../src/2d.js';
import { Motion, CollisionBox } from '../src/components.js';
import { EntityRegistry } from '../src/ecs.js';
import {Server} from 'socket.io';
import Express from 'express';
import http_server from 'http';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = Express();
const http = new http_server.Server(app);
const io = new Server(http);
const connections = new Set();
io.on("connection", socket => {
connections.add(socket);
console.log(`Connected client ${socket.id}.`);
socket.on('time-sync', callback => {
callback(performance.now());
});
socket.on('disconnect', () => {
connections.delete(socket);
});
});
function main() {
app.use('/', Express.static(path.join(__dirname, '../dist')));
const server_port = parseInt(process.env.PORT) | 3000;
http.listen(server_port, () => {
console.log(`Server started on port ${server_port}.`);
});
process.on('SIGINT', () => {
console.log('\nShutting server...');
connections.forEach(socket => {
socket.disconnect();
console.log(`Disconnected socket ${socket.id};`);
})
http.close(() => {
console.log('Terminated.');
process.exit(0);
});
});
let registry = new EntityRegistry();
let tank = registry.create();
registry.assign(tank, new Motion(new Vec2(0, 0), new Vec2(1, 1)));
registry.assign(tank, new CollisionBox(1));
setInterval(() => {
registry.forEach([Motion], (id, motion) => {
motion.position.addTo(motion.velocity);
});
}, 1000/60);
setInterval(() => {
io.emit("update", registry.get(tank, Motion))
}, 50);
}
main();

View File

@ -1,51 +0,0 @@
export class PriorityQueue {
constructor() {
this.val = [null];
}
push(value, priority) {
if (priority == null) {
priority = value;
}
this.val.push([value, priority]);
let id = this.val.length - 1;
while (id > 1 && this.val[id][1] > this.val[id >>> 1][1]) {
const kid = id >>> 1;
[this.val[id], this.val[kid]] = [this.val[kid], this.val[id]];
id = kid;
}
return this;
}
pop() {
let lv = this.val.pop();
if (this.val.length == 1) { return lv; }
let res = this.val[1];
this.val[1] = lv;
let id = 1;
while (id * 2 < this.val.length) {
if (this.val[id][1] > this.val[id * 2][1] && (id * 2 + 1 >= this.val.length || this.val[id][1] > this.val[id * 2 + 1][1])) {
break;
}
let kid = (id * 2 + 1 >= this.val.length || this.val[id * 2][1] > this.val[id * 2 + 1][1]) ? id * 2 : id * 2 + 1;
[this.val[id], this.val[kid]] = [this.val[kid], this.val[id]];
id = kid;
}
return res;
}
top() {
return this.val[1];
}
size() {
return this.val.length - 1;
}
empty() {
return this.size() == 0;
}
}

View File

@ -1,105 +0,0 @@
// FHQ Treap impl a set
class TreapNode {
constructor(val) {
this.value = val;
this.left = null;
this.right = null;
this.sz = 1;
this.rd = Math.random();
}
update() {
this.sz = (this.left?.sz || 0) + (this.right?.sz || 0) + 1;
}
static splitBySize(rt, k) {
if (rt == null) {
return [null, null];
}
let x, y;
if ((rt.left?.sz || 0) + 1 <= k) {
x = rt;
[rt.right, y] = TreapNode.splitBySize(rt.right, k - (rt.left?.sz || 0) - 1);
} else {
y = rt;
[x, rt.left] = TreapNode.splitBySize(rt.left, k);
}
rt.update();
return [x, y];
}
static splitByValue(rt, v) {
if (rt == null) {
return [null, null];
}
let x, y;
if (rt.value <= v) {
x = rt;
[rt.right, y] = TreapNode.splitByValue(rt.right, v);
} else {
y = rt;
[x, rt.left] = TreapNode.splitByValue(rt.left, v);
}
rt.update();
return [x, y];
}
static merge(x, y) {
if (x == null) {
return y;
} else if (y == null) {
return x;
}
if (x.rd < y.rd) {
y.left = TreapNode.merge(x, y.left);
y.update();
return y;
}
x.right = TreapNode.merge(x.right, y);
x.update();
return x;
}
static forEach(rt, func) {
if (rt == null) {
return;
}
TreapNode.forEach(rt.left, func);
func(rt.value);
TreapNode.forEach(rt.right, func);
}
}
export class TreapSet {
constructor() {
this.root = null;
}
insertRaw(v) {
let [x, y] = TreapNode.splitByValue(this.root, v);
this.root = TreapNode.merge(TreapNode.merge(x, new TreapNode(v)), y);
return this;
}
erase(v) {
let [x, p] = TreapNode.splitByValue(this.root, v - 1);
let [_, z] = TreapNode.splitByValue(p, v);
this.root = TreapNode.merge(x, z);
return this;
}
takeInstance() {
return this.root == null ? null : this.root.value;
}
forEach(func) {
TreapNode.forEach(this.root, func);
return this;
}
}

42
src/render/misc.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "render.h"
namespace {
class SDLLoader {
private:
SDLLoader() {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
IMG_Init(IMG_INIT_PNG);
}
~SDLLoader() {
IMG_Quit();
SDL_Quit();
}
static const SDLLoader _;
};
} // namespace
arras::Color::Color(): r(255), g(255), b(255), a(255) {}
arras::Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a): r(r), g(g), b(b), a(a) {}
std::shared_ptr<SDL_Rect> arras::makeRect(int x, int y, int w, int h) {
auto res = std::make_shared<SDL_Rect>();
res->x = x;
res->y = y;
res->w = w;
res->h = h;
return res;
}
std::shared_ptr<SDL_FRect> arras::makeFRect(const arras::Vec2& pos, const arras::Vec2& size) {
auto res = std::make_shared<SDL_FRect>();
res->x = pos.real();
res->y = pos.imag();
res->w = size.real();
res->h = size.imag();
return res;
}

53
src/render/surface.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "render.h"
using namespace arras;
RenderSurface::RenderSurface(RenderSurface&& rs): s(rs.s) {
rs.s = nullptr;
}
RenderSurface::RenderSurface(const RenderSurface& rs) {
s = SDL_CreateRGBSurfaceWithFormat(0, rs.width(), rs.height(), 32, SDL_PIXELFORMAT_RGBA8888);
if (!s) {
throw std::runtime_error(
fmt::format("SDL_CreateRGBSurfaceWithFormat() failed: {}", SDL_GetError()));
}
if (SDL_BlitSurface(rs.nativeHandle(),
nullptr,
s,
makeRect(0, 0, rs.width(), rs.height()).get())
!= 0) {
throw std::runtime_error(fmt::format("SDL_BlitSurface() failed: {}", SDL_GetError()));
}
}
RenderSurface::RenderSurface(SDL_RWops* src) {
s = IMG_Load_RW(src, 1);
if (!s) {
throw std::runtime_error(fmt::format("IMG_Load_RW() failed: {}", IMG_GetError()));
}
}
RenderSurface::RenderSurface(int width, int height) {
s = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGBA8888);
if (!s) {
throw std::runtime_error(
fmt::format("SDL_CreateRGBSurfaceWithFormat() failed: {}", SDL_GetError()));
}
}
RenderSurface ::~RenderSurface() {
SDL_FreeSurface(s);
}
SDL_Surface* RenderSurface::nativeHandle() const noexcept {
return s;
}
int RenderSurface::width() const noexcept {
return s->w;
}
int RenderSurface::height() const noexcept {
return s->h;
}

63
src/render/texture.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "render.h"
using namespace arras;
RenderTexture::RenderTexture(RenderTexture&& rt): t(rt.t) {
rt.t = nullptr;
}
RenderTexture::RenderTexture(const RenderWindow& rd, SDL_RWops* src) {
t = IMG_LoadTexture_RW(rd.nativeHandle(), src, 1);
if (!t) {
throw std::runtime_error(fmt::format("IMG_LoadTexture_RW() failed: {}", IMG_GetError()));
}
if (SDL_QueryTexture(t, nullptr, nullptr, &w, &h) != 0) {
throw std::runtime_error(fmt::format("SDL_QueryTexture() failed: {}", SDL_GetError()));
}
}
RenderTexture::RenderTexture(const RenderWindow& rd, const RenderSurface& rs) {
t = SDL_CreateTextureFromSurface(rd.nativeHandle(), rs.nativeHandle());
if (!t) {
throw std::runtime_error(
fmt::format("SDL_CreateRGBSurfaceWithFormat() failed: {}", SDL_GetError()));
}
if (SDL_QueryTexture(t, nullptr, nullptr, &w, &h)) {
throw std::runtime_error(fmt::format("SDL_QueryTexture() failed: {}", SDL_GetError()));
}
}
RenderTexture::~RenderTexture() {
SDL_DestroyTexture(t);
}
SDL_Texture* RenderTexture::nativeHandle() const noexcept {
return t;
}
int RenderTexture::width() const noexcept {
return w;
}
int RenderTexture::height() const noexcept {
return h;
}
std::pair<int, int> RenderTexture::calcRenderSize(int w, int h) const noexcept {
int rw, rh;
if (w == 0 && h == 0) {
rw = width();
rh = height();
} else if (w == 0) {
rw = (h * width() / height());
rh = h;
} else if (h == 0) {
rw = w;
rh = (w * height() / width());
} else {
rw = w;
rh = h;
}
return {rw, rh};
}

76
src/render/window.cpp Normal file
View File

@ -0,0 +1,76 @@
#ifndef HEADER_ARRAS_RENDER_WINDOW_HPP
#define HEADER_ARRAS_RENDER_WINDOW_HPP
#include "render.h"
#define Pi 3.14159265358979323846
using namespace arras;
RenderWindow::RenderWindow(RenderWindow&& rd)
: w(rd.w), h(rd.h), window(rd.window), renderer(rd.renderer) {
rd.renderer = nullptr;
rd.window = nullptr;
}
RenderWindow::RenderWindow(int w, int h): w(w), h(h) {
if (SDL_CreateWindowAndRenderer(w, h, SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI, &window, &renderer) != 0) {
throw std::runtime_error(
fmt::format("SDL_CreateWindowAndRenderer() failed: {}", SDL_GetError()));
}
}
RenderWindow::~RenderWindow() {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
}
SDL_Renderer* RenderWindow::nativeHandle() const noexcept {
return renderer;
}
void RenderWindow::renderTextureAt(const RenderTexture& t, const Vec2& pos) const {
if (SDL_RenderCopyF(renderer,
t.nativeHandle(),
NULL,
makeFRect(pos, {t.width(), t.height()}).get())) {
throw std::runtime_error(fmt::format("SDL_RenderCopy() failed: {}", SDL_GetError()));
}
}
void RenderWindow::renderTextureAt(const RenderTexture& t,
const Vec2& pos,
const Vec2& direction) const {
if (SDL_RenderCopyExF(renderer,
t.nativeHandle(),
NULL,
makeFRect(pos, {t.width(), t.height()}).get(),
-asAngle(direction) * 180 / Pi,
nullptr,
SDL_FLIP_NONE)) {
throw std::runtime_error(fmt::format("SDL_RenderCopy() failed: {}", SDL_GetError()));
}
}
void RenderWindow::present() const noexcept {
SDL_RenderPresent(renderer);
}
void RenderWindow::clear(const Color& c) const {
if (SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a)) {
throw std::runtime_error(
fmt::format("SDL_SetRenderDrawColor() failed: {}", SDL_GetError()));
}
if (SDL_RenderClear(renderer)) {
throw std::runtime_error(fmt::format("SDL_RenderClear() failed: {}", SDL_GetError()));
}
}
int RenderWindow::width() const noexcept {
return w;
}
int RenderWindow::height() const noexcept {
return h;
}
#endif

60
test/cases/2d.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "2d.h"
#include "test/u.hpp"
#include <functional>
#include <initializer_list>
namespace {
AddTestCase _(1, "2d.hpp", [](TestCase& t) {
using namespace arras;
t.expectEq<Float>([] { return cross({1, 2}, {4, 1}); }, -7);
t.expectEq<Float>([] { return cross({.5, 2.5}, {4, 1.5}); }, -9.25);
t.expectEq<Float>([] { return dot({1, 2}, {4, 1}); }, 6);
t.expectTrue([] { return Circle({0, 0}, 2).contain({1, 1}); });
t.expectTrue([] { return Circle({0, 0}, 1).contain({0, 1}); });
t.expectFalse([] { return Circle({1, 0}, 1.4).contain({0, -1}); });
t.expectTrue([] { return Circle({1, 1}, 1.42).contain({0, 0}); });
t.expectTrue([] { return Circle({1.83649, -.12711}, .05484).contain({1.82541, -.1019}); });
t.expectEq<Vec2>([] { return Polygon<3>{{0, 0}, {1, 0}, {1, 1}}.vertex[0]; }, {0, 0});
t.expectTrue([] { return Polygon<3>{{0, 0}, {1, 0}, {1, 1}}.contain({.1, .05}); });
t.expectFalse([] { return Polygon<3>{{0, 0}, {1, 0}, {1, 1}}.contain({.1, .2}); });
Polygon<9> pA{{-2, -2}, {-1, -2}, {2, 0}, {1, -2}, {2, -1}, {2, -3}, {3, 1}, {1, 1}, {0, 0}};
t.expectTrue([pA] { return pA.contain({1, 0}); });
t.expectTrue([pA] { return pA.contain({0, 0}); });
t.expectTrue([pA] { return pA.contain({0, -1}); });
t.expectTrue([pA] { return pA.contain({1.52, -1.26}); });
t.expectTrue([pA] { return pA.contain({2.16, -1.9}); });
t.expectFalse([pA] { return pA.contain({1.28, -.8}); });
t.expectFalse([pA] { return pA.contain({1.68, -1.94}); });
t.expectFalse([pA] { return pA.contain({1.83649, -.12711}); });
t.expectTrue([pA] { return pA.contain({1.82541, -.1019}); });
t.expectTrue([] { return intersect(Circle({1, 1}, 2), Circle({1, 1}, 1)); });
t.expectTrue([] { return intersect(Circle({1, 1}, 1.5), Circle({3, 1}, 1.5)); });
t.expectFalse([] { return intersect(Circle({1, 1}, 1.5), Circle({3, 4}, 1.5)); });
t.expectFalse([] { return intersect(Circle({0, 0}, 1.5), Circle({-3, -4}, 1.5)); });
t.expectTrue([] { return intersect(Circle({1, 1}, 2), Polygon<3>{{0, 0}, {2, 2}, {0, 2}}); });
t.expectTrue([] { return intersect(Polygon<3>{{0, 0}, {2, 2}, {0, 2}}, Circle({1, 1}, 2)); });
t.expectTrue([] { return intersect(Circle({0, 0}, 5), Polygon<4>{{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}); });
t.expectTrue([] { return intersect(Circle({0, 0}, 1), Polygon<4>{{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}); });
t.expectTrue([] { return intersect(Circle({0, 0}, 1), Polygon<4>{{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}); });
t.expectTrue([] { return intersect(Circle({0, 0}, 1), Polygon<3>{{-1, -1}, {-1, 1}, {1, 1}}); });
t.expectFalse([] { return intersect(Circle({0, 0}, 2), Polygon<3>{{0, -3}, {14, -3}, {0, -5}}); });
t.expectFalse([pA] { return intersect(Circle({-1, 1}, 1.4), pA); });
t.expectTrue([pA] { return intersect(Circle({-3, -2}, 1.2), pA); });
t.expectTrue([pA] { return intersect(Circle({1.83649, -.12711}, .05484), pA); });
t.expectFalse([pA] { return intersect(Circle({1.5595, -.42437}, .08794), pA); });
t.expectTrue([] { return intersect(Polygon<4>{{3, 1}, {0, 1}, {0, 0}, {3, 0}}
, Polygon<4>{{.1, .1}, {.2, .1}, {.2, .2}, {.1, .2}}); });
t.expectFalse([pA] { return intersect(pA, Polygon<3>{{1.5595, -.42437}, {1.64733, -.41987}, {1.83649, -.12711}}); });
t.expectTrue([pA] { return intersect(pA, Polygon<3>{{1.5595, -.42437}, {1.64733, -.41987}, {1.82541, -.1019}}); });
});
}

10
test/cases/objects.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "objects.hpp"
#include "test/u.hpp"
namespace {
AddTestCase _(1, "objects.hpp", [](TestCase& t) {
});
}

19
test/cases/provider.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "test/u.hpp"
#include <chrono>
#include <thread>
namespace {
AddTestCase _(0, "tester", [](TestCase& t) {
t.expectTrue([] { return true; });
t.expectFalse([] { return false; });
t.expectTrue([] {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return true;
});
t.expectEq<int>([] { return 42; }, 42);
t.expectEq<float>([] { return 10 + 1e-7; }, 10);
t.expectEq<double>([] { return 10 + 1e-10; }, 10);
});
}

5
test/main.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "test/u.hpp"
int main() {
return TestSuit::getInstance()->evalTestCases();
}

204
test/u.hpp Normal file
View File

@ -0,0 +1,204 @@
#ifndef HEADER_TEST_U_HPP
#define HEADER_TEST_U_HPP
#include <chrono>
#include <cstdio>
#include <exception>
#include <functional>
#include <optional>
#include <type_traits>
#include <tuple>
#include <vector>
#include <algorithm>
template<typename T>
struct TestDefaultEps {};
template<>
struct TestDefaultEps<float> {
static constexpr float value = 1e-6;
};
template<>
struct TestDefaultEps<double> {
static constexpr double value = 1e-9;
};
template<>
struct TestDefaultEps<long double> {
static constexpr long double value = 1e-12;
};
class TestCase {
public:
TestCase(int id, const char* title): ok(0), fail(0), n(0), ended(false), time_used(0) {
std::printf("[%02d]Testing %s:\n", id, title);
}
void expectTrue(std::function<bool()> func) noexcept {
expectEq<bool>(func, true);
}
void expectFalse(std::function<bool()> func) noexcept {
expectEq<bool>(func, false);
}
template<typename T, typename EPS = TestDefaultEps<T>>
typename std::enable_if<std::is_floating_point<T>::value>::type expectEq(
std::function<T()> func,
T&& answer) noexcept {
auto res = runTask(func);
if (!res.has_value()) {
return onError();
}
if (std::abs(res.value() - answer) < EPS::value) {
onPass();
} else {
onFail();
}
}
template<typename T>
typename std::enable_if<!std::is_floating_point<T>::value>::type expectEq(
std::function<T()> func,
T&& answer) noexcept {
auto res = runTask(func);
if (!res.has_value()) {
return onError();
}
if (res.value() == answer) {
onPass();
} else {
onFail();
}
}
void end() noexcept {
if (ended) {
return;
}
ended = true;
std::putchar('\n');
for (int i = 0; i < 20; ++i) {
std::putchar('-');
}
std::putchar('\n');
if (fail) {
if (fail == 1) {
std::puts("1 test failed.");
} else {
std::printf("%d tests failed.\n", fail);
}
} else {
if (ok == 1) {
std::printf("Ok. 1 test passed");
} else {
std::printf("Ok. %d tests passed", ok);
}
std::printf(" in %lldms.\n", static_cast<long long>(time_used.count()));
}
}
bool hasFail() const noexcept {
return fail > 0;
}
private:
void putchar(char c) noexcept {
n += 1;
if (n > 1 && n % 15 == 1) {
std::putchar('\n');
}
std::putchar(c);
}
void onPass() noexcept {
ok += 1;
putchar('.');
}
void onFail() noexcept {
fail += 1;
putchar('F');
}
void onError() noexcept {
fail += 1;
putchar('E');
}
template<typename T>
std::optional<T> runTask(std::function<T()> func) noexcept {
auto start = std::chrono::high_resolution_clock::now();
try {
T res = func();
auto end = std::chrono::high_resolution_clock::now();
auto dt = end - start;
time_used += std::chrono::duration_cast<std::chrono::milliseconds>(dt);
return {res};
} catch (...) {
return std::nullopt;
}
}
int ok, fail, n;
bool ended;
std::chrono::milliseconds time_used;
};
class TestSuit {
TestSuit(const TestSuit&) = delete;
TestSuit(TestSuit&&) = delete;
public:
static TestSuit* getInstance() {
if (!instance) {
instance = new TestSuit();
}
return instance;
}
void addTestCase(int id, const char* title, std::function<void(TestCase&)> f) {
tc.emplace_back(id, title, f);
}
int evalTestCases() {
std::sort(tc.begin(), tc.end(), [](const auto &x, const auto &y) {
return std::get<0>(x) < std::get<0>(y);
});
for (auto [id, title, func] : tc) {
TestCase t(id, title);
func(t);
t.end();
std::putchar('\n');
if (t.hasFail()) {
return 1;
}
}
tc.clear();
return 0;
}
private:
static inline TestSuit* instance = nullptr;
TestSuit() {}
std::vector<std::tuple<int, const char*, std::function<void(TestCase&)>>> tc;
};
class AddTestCase {
AddTestCase() = delete;
AddTestCase(const AddTestCase&) = delete;
AddTestCase(AddTestCase&&) = delete;
public:
AddTestCase(int id, const char* tilte, std::function<void(TestCase&)> func) {
TestSuit::getInstance()->addTestCase(id, tilte, func);
}
};
#endif // HEADER_TEST_U_HPP

View File

@ -1,68 +0,0 @@
import assert from 'node:assert';
import { Vec2, Circle } from '../src/2d.js';
describe('Vec2', function () {
it('construct', function() {
let p = new Vec2(1, 2);
assert.equal(p.x, 1);
assert.equal(p.y, 2);
});
it('equal', function() {
assert.ok((new Vec2(1, 2)).equal(new Vec2(1, 2)));
assert.ok(!(new Vec2(1, 2)).equal(new Vec2(1, 12)));
});
it('vector calculation', function() {
let p = new Vec2(2, 4), q = new Vec2(3, 1);
assert.ok(p.add(q).equal(new Vec2(5, 5)));
assert.ok(p.sub(q).equal(new Vec2(-1, 3)));
assert.ok(p.mul(1.5).equal(new Vec2(3, 6)));
assert.ok(p.yx().equal(new Vec2(4, 2)));
});
it('vector calculation(TO)', function () {
let p = new Vec2(2, 4), q = new Vec2(3, 1);
p.addTo(q);
assert.ok(p.equal(new Vec2(5, 5)));
p.subTo(q);
assert.ok(p.equal(new Vec2(2, 4)));
p.mulTo(1.5);
assert.ok(p.equal(new Vec2(3, 6)));
});
it('abs & norm', function() {
let p = new Vec2(3, 4);
assert.equal(p.abs(), 5);
assert.equal(p.norm(), 25);
});
it('complex calculation', function() {
let p = new Vec2(1, 4), q = new Vec2(0, 1);
assert.ok(p.complexMul(q).equal(new Vec2(-4, 1)));
assert.ok(p.complexDiv(q).equal(new Vec2(4, -1)));
assert.equal(q.arg(), Math.PI / 2);
});
it('dot & cross product', function() {
let p = new Vec2(2, 4), q = new Vec2(3, 1);
assert.equal(p.dot(q), 10);
assert.equal(p.cross(q), -10);
});
it('circle intersect', function() {
let c1 = new Circle(new Vec2(0, 0), 1);
let c2 = new Circle(new Vec2(0, 1.5), 1);
let c3 = new Circle(new Vec2(2, 2), 1);
assert.ok(Circle.intersect(c1, c2));
assert.ok(!Circle.intersect(c1, c3));
});
it('circle collision elimation', function() {
let c1 = new Circle(new Vec2(0, 0), 1);
let c2 = new Circle(new Vec2(0, 1.5), 1);
assert.ok(Circle.collisionElimation(c1, c2));
assert.ok(c1.equal(new Circle(new Vec2(0, -.25), 1)));
assert.ok(c2.equal(new Circle(new Vec2(0, 1.75), 1)));
});
});

View File

@ -1,59 +0,0 @@
import assert from 'node:assert';
import { EntityRegistry } from '../src/ecs.js';
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
describe('ECS', function () {
it('create', function () {
let registry = new EntityRegistry();
let id = registry.create();
});
it('destory', function () {
let registry = new EntityRegistry();
let id0 = registry.create();
let id1 = registry.destory(id0).create();
assert.equal(id0, id1);
});
it('component', function () {
let registry = new EntityRegistry();
let id3;
for (let i = 0; i < 5; i += 1) {
let e = registry.create();
registry.assign(e, new Position(i, i));
if (i == 3) {
id3 = e;
}
}
assert.equal(registry.get(id3, Position).x, 3);
});
it('forEach', function () {
let registry = new EntityRegistry();
let id3;
registry.create();
registry.create();
for (let i = 0; i < 5; i += 1) {
let e = registry.create();
registry.assign(e, new Position(i, i));
if (i == 3) {
id3 = e;
}
}
let count = 0;
registry.forEach([], () => {
count += 1;
});
assert.equal(count, 7);
registry.forEach([Position], (id, pos) => {
pos.x += 1;
});
assert.equal(registry.get(id3, Position).x, 4);
});
});

View File

@ -1,18 +0,0 @@
import assert from 'node:assert';
import { PriorityQueue } from '../src/misc/priority_queue.js';
describe('PriorityQueue', function () {
it('everything', function() {
let q = new PriorityQueue();
q.push(1).push(2).push(-1).push('hi', 4);
assert.equal(q.size(), 4);
assert.equal(q.top()[0], 'hi');
assert.equal(q.pop()[0], 'hi');
assert.equal(q.pop()[0], 2);
assert.equal(q.pop()[0], 1);
assert.equal(q.pop()[0], -1);
assert.ok(q.empty());
});
});

View File

@ -1,25 +0,0 @@
import assert from 'node:assert';
import { TreapSet } from '../src/misc/treap.js';
describe('TreapSet', function () {
it('hasInstance', function() {
let t = new TreapSet();
assert.equal(t.takeInstance(), null);
t.insertRaw(2);
assert.equal(t.takeInstance(), 2);
});
it('erase', function () {
let t = new TreapSet();
t.insertRaw(2).insertRaw(3).erase(2);
assert.equal(t.takeInstance(), 3);
});
it('forEach', function() {
let t = new TreapSet();
t.insertRaw(2).insertRaw(3).insertRaw(-1).insertRaw(0).insertRaw(10);
let a = [];
t.forEach(x => a.push(x));
assert.deepEqual(a, [-1, 0, 2, 3, 10])
});
});

34
xmake.lua Normal file
View File

@ -0,0 +1,34 @@
set_project("arras")
set_basename("arras")
set_version("0.0a0")
set_languages("c++17")
set_targetdir(".")
add_requires("entt 3.11", "libsdl 2.26", "libsdl_image 2.6", "fmt 10")
add_includedirs("include", ".")
target("main")
set_default(true)
set_kind("binary")
add_files("main.cpp")
add_files("src/**/*.cpp")
add_packages("entt", "libsdl", "libsdl_image", "fmt")
set_warnings("allextra")
target("test")
set_default(false)
set_kind("binary")
set_prefixname("test-")
add_files("test/main.cpp")
add_files("test/cases/*.cpp")
add_files("src/**/*.cpp")
add_packages("entt", "fmt")
set_warnings("allextra")
set_optimize("none")
set_symbols("debug")
add_defines("DEBUG")