We're writing unit tests in Zig now
I dunno what happened, I was just applying some FloatFree(tm) and then all of this appeared out of nowhere!
This commit is contained in:
parent
0afba2955e
commit
ffa5a92ef0
14 changed files with 65 additions and 390 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -26,3 +26,4 @@ Win32_LIB_ASM_Release
|
|||
/bin
|
||||
/build
|
||||
/CMakeUserPresets.json
|
||||
/.zig-cache
|
||||
|
|
|
|||
27
build.zig
Normal file
27
build.zig
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// this is just unit tests for now, so the root source file is the tests file
|
||||
const blankart = b.addModule("blankart", .{
|
||||
.root_source_file = b.path("src/tests.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
blankart.addIncludePath(b.path("src")); // -Isrc
|
||||
blankart.linkSystemLibrary("c", .{}); // -lc
|
||||
blankart.addCSourceFiles(.{
|
||||
.root = b.path("src"),
|
||||
// anything you need to test goes here
|
||||
.files = &.{ "m_fixed.c", "tables.c" },
|
||||
});
|
||||
|
||||
const blantests = b.addTest(.{
|
||||
.root_module = blankart,
|
||||
});
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&b.addRunArtifact(blantests).step);
|
||||
}
|
||||
|
|
@ -1661,7 +1661,6 @@ static const char *CON_LoadingStrings[LOADED_ALLDONE+1] =
|
|||
"Init rendering daemon...", //LOADED_RINIT
|
||||
"Init audio subsystem...", //LOADED_SINITSFXCHANNELS
|
||||
"Cache HUD...", //LOADED_STINIT
|
||||
"Test fixed-point arithmetic...", //LOADED_MATHINIT
|
||||
"Init ACSVM...", //LOADED_ACSINIT
|
||||
"Check game status...", //LOADED_DCHECKNETGAME
|
||||
"Now starting..."
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ typedef enum
|
|||
LOADED_RINIT,
|
||||
LOADED_SINITSFXCHANNELS,
|
||||
LOADED_STINIT,
|
||||
LOADED_MATHINIT,
|
||||
LOADED_ACSINIT,
|
||||
LOADED_DCHECKNETGAME,
|
||||
LOADED_ALLDONE = LOADED_DCHECKNETGAME,
|
||||
|
|
|
|||
|
|
@ -1873,61 +1873,6 @@ void D_SRB2Main(void)
|
|||
ST_Init();
|
||||
CON_SetLoadingProgress(LOADED_STINIT);
|
||||
|
||||
CONS_Printf("FixedSqrt of 32767 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
|
||||
FixedSqrt(0x7FFF0000),
|
||||
FixedSqrt64(0x7FFF0000));
|
||||
|
||||
if (FixedSqrt(0x7FFF0000) == FixedSqrt64(0x7FFF0000))
|
||||
{
|
||||
CONS_Printf("\x83" "32767: Test OK!" "\x80" "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
CONS_Printf("\x85" "32767: Test NG!" "\x80" "\n");
|
||||
}
|
||||
|
||||
CONS_Printf("FixedSqrt of 4 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
|
||||
FixedSqrt(0x40000),
|
||||
FixedSqrt64(0x40000));
|
||||
|
||||
if (FixedSqrt(0x40000) == FixedSqrt64(0x40000))
|
||||
{
|
||||
CONS_Printf("\x83" "4: Test OK!" "\x80" "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
CONS_Printf("\x85" "4: Test NG!" "\x80" "\n");
|
||||
}
|
||||
|
||||
// You should probably generate the weird number with RANDOM.org
|
||||
#define WEIRDNUMBER 3886284 /* 59.3 fracunits; this should approximate to around 7 */
|
||||
CONS_Printf("FixedSqrt of 59.3 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
|
||||
FixedSqrt(WEIRDNUMBER),
|
||||
FixedSqrt64(WEIRDNUMBER));
|
||||
|
||||
if (FixedSqrt(WEIRDNUMBER) == FixedSqrt64(WEIRDNUMBER))
|
||||
{
|
||||
CONS_Printf("\x83" "59.3: Test OK!" "\x80" "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
CONS_Printf("\x85" "59.3: Test NG!" "\x80" "\n");
|
||||
}
|
||||
#undef WEIRDNUMBER
|
||||
|
||||
CONS_Printf("FixedSqrt64 of 65535 fracunits: %" PRId64 "(not possible at 32-bit scale)\n",
|
||||
FixedSqrt64(0xFFFFFFFF));
|
||||
|
||||
CONS_Printf("IntSqrt of 32767: %d; IntSqrt64: %" PRId64 "\n",
|
||||
IntSqrt(0x7FFF),
|
||||
IntSqrt64(0x7FFF));
|
||||
|
||||
CONS_Printf("IntSqrt of 4: %d; IntSqrt64: %" PRId64 "\n",
|
||||
IntSqrt(4),
|
||||
IntSqrt64(4));
|
||||
|
||||
CON_SetLoadingProgress(LOADED_MATHINIT);
|
||||
|
||||
CONS_Printf("ACS_Init(): Init Action Code Script VM.\n");
|
||||
ACS_Init();
|
||||
CON_SetLoadingProgress(LOADED_ACSINIT);
|
||||
|
|
|
|||
|
|
@ -509,16 +509,8 @@ UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush)
|
|||
|
||||
static INT32 K_KartGetSMonitorOdds(UINT32 dist)
|
||||
{
|
||||
// I'm tired; use floating-point distances for this fuckshit.
|
||||
INT32 monitordist = SMONITORDIST;
|
||||
|
||||
float fac_f = (float)(dist) / ((float)(monitordist));
|
||||
|
||||
if (fac_f < 1.0f)
|
||||
return 0;
|
||||
|
||||
// If you're far enough for this to be in your item slot, you're far enough to SERIOUSLY need this.
|
||||
return MAXSMONITORODDS;
|
||||
return dist >= (UINT32)SMONITORDIST ? MAXSMONITORODDS : 0;
|
||||
}
|
||||
|
||||
// updates all result cooldown timers, and sets cooldowns for "unique" items
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ extern boolean forcefullinvintheme;
|
|||
boolean K_PlayFullInvinTheme(void);
|
||||
void K_DoInvincibility(player_t *player, tic_t time);
|
||||
void K_DoSMonitor(player_t *player, tic_t time);
|
||||
#define SMONITORWARNINGTIME (fixed_t)(0.833333333f * (float)(FRACUNIT))
|
||||
#define SMONITORWARNINGTIME (5*FRACUNIT/6)
|
||||
fixed_t K_SMonitorGradient(UINT16 time);
|
||||
fixed_t K_GetSMonitorSpeed(UINT16 time);
|
||||
fixed_t K_GetSMonitorAccel(UINT16 time);
|
||||
|
|
|
|||
258
src/m_fixed.c
258
src/m_fixed.c
|
|
@ -11,30 +11,12 @@
|
|||
/// \file m_fixed.c
|
||||
/// \brief Fixed point implementation
|
||||
|
||||
#if 0 //#ifndef NO_M
|
||||
#include <math.h>
|
||||
#define HAVE_SQRT
|
||||
#if 0 //#ifndef _WIN32 // MSVCRT does not have *f() functions
|
||||
#define HAVE_SQRTF
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "doomdef.h"
|
||||
#include "m_fixed.h"
|
||||
#include "tables.h" // ANGLETOFINESHIFT
|
||||
|
||||
fixed_t FixedSqrt(fixed_t x)
|
||||
{
|
||||
#ifdef HAVE_SQRT
|
||||
const float fx = FIXED_TO_FLOAT(x);
|
||||
float fr;
|
||||
#ifdef HAVE_SQRTF
|
||||
fr = sqrtf(fx);
|
||||
#else
|
||||
fr = (float)sqrt(fx);
|
||||
#endif
|
||||
return FLOAT_TO_FIXED(fr);
|
||||
#else
|
||||
// The neglected art of Fixed Point arithmetic
|
||||
// Jetro Lauha
|
||||
// Seminar Presentation
|
||||
|
|
@ -58,22 +40,11 @@ fixed_t FixedSqrt(fixed_t x)
|
|||
}
|
||||
} while (count-- != 0);
|
||||
return root;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Shitty 64-bit duplicate so certain distancing edgecases in k_odds die
|
||||
INT64 FixedSqrt64(INT64 x)
|
||||
{
|
||||
#ifdef HAVE_SQRT
|
||||
const float fx = FixedToFloat64(x);
|
||||
float fr;
|
||||
#ifdef HAVE_SQRTF
|
||||
fr = sqrtf(fx);
|
||||
#else
|
||||
fr = (float)sqrt(fx);
|
||||
#endif
|
||||
return FloatToFixed64(fr);
|
||||
#else
|
||||
// The neglected art of Fixed Point arithmetic
|
||||
// Jetro Lauha
|
||||
// Seminar Presentation
|
||||
|
|
@ -97,7 +68,6 @@ INT64 FixedSqrt64(INT64 x)
|
|||
}
|
||||
} while (count-- != 0);
|
||||
return root;
|
||||
#endif
|
||||
}
|
||||
|
||||
fixed_t FixedHypot(fixed_t x, fixed_t y)
|
||||
|
|
@ -1242,231 +1212,3 @@ fixed_t ApproachFixed(fixed_t current, fixed_t target, fixed_t inc, fixed_t dec)
|
|||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/** \brief Returns the floating-point value 'current' after it tries to approach target, going up at
|
||||
most 'inc' and going down at most 'dec'.
|
||||
|
||||
\param current (float) current value
|
||||
|
||||
\param target (float) target value
|
||||
|
||||
\param inc (float) value to increment by
|
||||
|
||||
\param dec (float) value to decrement by
|
||||
|
||||
\return (float) current value after incrementing/decrementing
|
||||
*/
|
||||
float ApproachFloat(float current, float target, float inc, float dec)
|
||||
{
|
||||
if (current < target)
|
||||
{
|
||||
current += inc;
|
||||
if (current > target)
|
||||
{
|
||||
current = target;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current -= dec;
|
||||
if (current < target)
|
||||
{
|
||||
current = target;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/** \brief Returns the double-precision floating-point value 'current' after it tries to approach
|
||||
target, going up at most 'inc' and going down at most 'dec'.
|
||||
|
||||
\param current (double) current value
|
||||
|
||||
\param target (double) target value
|
||||
|
||||
\param inc (double) value to increment by
|
||||
|
||||
\param dec (double) value to decrement by
|
||||
|
||||
\return (double) current value after incrementing/decrementing
|
||||
*/
|
||||
double ApproachDouble(double current, double target, double inc, double dec)
|
||||
{
|
||||
if (current < target)
|
||||
{
|
||||
current += inc;
|
||||
if (current > target)
|
||||
{
|
||||
current = target;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current -= dec;
|
||||
if (current < target)
|
||||
{
|
||||
current = target;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
#ifdef M_TESTCASE
|
||||
//#define MULDIV_TEST
|
||||
#define SQRT_TEST
|
||||
|
||||
static inline void M_print(INT64 a)
|
||||
{
|
||||
const fixed_t w = (a >> FRACBITS);
|
||||
fixed_t f = a % FRACUNIT;
|
||||
fixed_t d = FRACUNIT;
|
||||
|
||||
if (f == 0)
|
||||
{
|
||||
printf("%d", (fixed_t)w);
|
||||
return;
|
||||
}
|
||||
else while (f != 1 && f / 2 == f >> 1)
|
||||
{
|
||||
d /= 2;
|
||||
f /= 2;
|
||||
}
|
||||
|
||||
if (w == 0)
|
||||
printf("%d/%d", (fixed_t)f, d);
|
||||
else
|
||||
printf("%d+(%d/%d)", (fixed_t)w, (fixed_t)f, d);
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static inline fixed_t FixedMulC(fixed_t a, fixed_t b)
|
||||
{
|
||||
return (fixed_t)((((INT64)a * b)) / FRACUNIT);
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static inline INT64 FixedMul64C(fixed_t a, fixed_t b)
|
||||
{
|
||||
return (INt64)((((INT64)a * b)) / FRACUNIT);
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
|
||||
{
|
||||
INT64 ret;
|
||||
|
||||
if (b == 0)
|
||||
I_Error("FixedDiv: divide by zero");
|
||||
|
||||
ret = (((INT64)a * FRACUNIT)) / b;
|
||||
|
||||
if ((ret > INT32_MAX) || (ret < INT32_MIN))
|
||||
I_Error("FixedDiv: divide by zero");
|
||||
return (fixed_t)ret;
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static inline fixed_t FixedDivC(fixed_t a, fixed_t b)
|
||||
{
|
||||
if ((abs(a) >> (FRACBITS - 2)) >= abs(b))
|
||||
return (a^b) < 0 ? INT32_MIN : INT32_MAX;
|
||||
|
||||
return FixedDivC2(a, b);
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static inline fixed_t FixedSqrtC(fixed_t x)
|
||||
{
|
||||
const float fx = FIXED_TO_FLOAT(x);
|
||||
float fr;
|
||||
#ifdef HAVE_SQRTF
|
||||
fr = sqrtf(fx);
|
||||
#else
|
||||
fr = (float)sqrt(fx);
|
||||
#endif
|
||||
return FLOAT_TO_FIXED(fr);
|
||||
}
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int n = 10;
|
||||
INT64 a, b;
|
||||
fixed_t c, d;
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
#ifdef MULDIV_TEST
|
||||
for (a = 1; a <= INT32_MAX; a += FRACUNIT)
|
||||
for (b = 0; b <= INT32_MAX; b += FRACUNIT)
|
||||
{
|
||||
c = FixedMul(a, b);
|
||||
d = FixedMulC(a, b);
|
||||
if (c != d)
|
||||
{
|
||||
printf("(");
|
||||
M_print(a);
|
||||
printf(") * (");
|
||||
M_print(b);
|
||||
printf(") = (");
|
||||
M_print(c);
|
||||
printf(") != (");
|
||||
M_print(d);
|
||||
printf(") \n");
|
||||
n--;
|
||||
printf("%d != %d\n", c, d);
|
||||
}
|
||||
c = FixedDiv(a, b);
|
||||
d = FixedDivC(a, b);
|
||||
if (c != d)
|
||||
{
|
||||
printf("(");
|
||||
M_print(a);
|
||||
printf(") / (");
|
||||
M_print(b);
|
||||
printf(") = (");
|
||||
M_print(c);
|
||||
printf(") != (");
|
||||
M_print(d);
|
||||
printf(")\n");
|
||||
n--;
|
||||
printf("%d != %d\n", c, d);
|
||||
}
|
||||
if (n <= 0)
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SQRT_TEST
|
||||
for (a = 0; a <= INT32_MAX; a += 1)
|
||||
{
|
||||
c = FixedSqrt(a);
|
||||
d = FixedSqrtC(a);
|
||||
b = abs(c - d);
|
||||
if (b > 1)
|
||||
{
|
||||
printf("sqrt(");
|
||||
M_print(a);
|
||||
printf(") = {(");
|
||||
M_print(c);
|
||||
printf(") != (");
|
||||
M_print(d);
|
||||
printf(")} \n");
|
||||
//n--;
|
||||
printf("%d != %d {", c, d);
|
||||
M_print(b);
|
||||
printf("}\n");
|
||||
}
|
||||
if (n <= 0)
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void *cpu_cpy(void *dest, const void *src, size_t n)
|
||||
{
|
||||
return memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
void *(*memcpy)(void* dest, const void* src, size_t n) = cpu_cpy;
|
||||
|
||||
void I_Error(const char *error, ...)
|
||||
{
|
||||
(void)error;
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -57,21 +57,11 @@ FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat(fixed_t x)
|
|||
return x / (float)FRACUNIT;
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static ATTRINLINE float FixedToFloat64(INT64 x)
|
||||
{
|
||||
return (float)x / (float)FRACUNIT;
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FloatToFixed(float f)
|
||||
{
|
||||
return (fixed_t)(f * FRACUNIT);
|
||||
}
|
||||
|
||||
FUNCMATH FUNCINLINE static ATTRINLINE INT64 FloatToFixed64(float f)
|
||||
{
|
||||
return (INT64)(f * FRACUNIT);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief convert fixed_t into double-precision floating number
|
||||
*/
|
||||
|
|
@ -469,8 +459,6 @@ void FM_Scale(matrix_t *dest, fixed_t x, fixed_t y, fixed_t z);
|
|||
INT32 ApproachINT32(INT32 current, INT32 target, INT32 inc, INT32 dec);
|
||||
UINT32 ApproachUINT32(UINT32 current, UINT32 target, UINT32 inc, UINT32 dec);
|
||||
fixed_t ApproachFixed(fixed_t current, fixed_t target, fixed_t inc, fixed_t dec);
|
||||
float ApproachFloat(float current, float target, float inc, float dec);
|
||||
double ApproachDouble(double current, double target, double inc, double dec);
|
||||
|
||||
/** \brief Returns the INT32 value ``current`` after it tries to approach ``target``, going up
|
||||
or down at most ``delta``.
|
||||
|
|
@ -505,8 +493,6 @@ double ApproachDouble(double current, double target, double inc, double dec);
|
|||
#define ApproachOneWayUINT32(current, target, delta) (ApproachUINT32(current, target, delta, delta))
|
||||
|
||||
#define ApproachOneWayFixed(current, target, delta) (ApproachFixed(current, target, delta, delta))
|
||||
#define ApproachOneWayFloat(current, target, delta) (ApproachFloat(current, target, delta, delta))
|
||||
#define ApproachOneWayDouble(current, target, delta) (ApproachDouble(current, target, delta, delta))
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
|
|
|||
35
src/tests.zig
Normal file
35
src/tests.zig
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const std = @import("std");
|
||||
|
||||
const C = @cImport({
|
||||
// include the headers you need for testing here
|
||||
// also make sure to add the corresponding source files in build.zig until
|
||||
// the symbol errors go away :face_holding_back_tears:
|
||||
@cInclude("m_fixed.h");
|
||||
});
|
||||
|
||||
const fixed_t = i32;
|
||||
const FRACUNIT: fixed_t = 65536;
|
||||
const FRACBITS = 16;
|
||||
|
||||
test "FixedSqrt d_main.cpp"
|
||||
{
|
||||
try std.testing.expectEqual(C.FixedSqrt(0x7FFF0000), C.FixedSqrt64(0x7FFF0000));
|
||||
try std.testing.expectEqual(C.FixedSqrt(0x40000), C.FixedSqrt64(0x40000));
|
||||
|
||||
// You should probably generate the weird number with RANDOM.org
|
||||
const WEIRDNUMBER = 3886284; // 59.3 fracunits; this should approximate to around 7
|
||||
try std.testing.expectEqual(C.FixedSqrt(WEIRDNUMBER), C.FixedSqrt64(WEIRDNUMBER));
|
||||
}
|
||||
|
||||
|
||||
test "FixedSqrt m_fixed.c"
|
||||
{
|
||||
var a: i64 = 0;
|
||||
// not enough time in the world for all 2 billion values...
|
||||
// prime numbers to the rescue
|
||||
while (a <= std.math.maxInt(i32)) : (a += 97) {
|
||||
const c: fixed_t = C.FixedSqrt(@intCast(a));
|
||||
const d: fixed_t = @intFromFloat(@sqrt(@as(f64, @floatFromInt(a)) / FRACUNIT) * FRACUNIT);
|
||||
try std.testing.expect(@abs(c - d) <= 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
target_sources(srb2tests PRIVATE
|
||||
boolcompat.cpp
|
||||
notnull.cpp
|
||||
testbase.hpp
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#include "testbase.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "../doomtype.h"
|
||||
|
||||
TEST_CASE("C++ bool is convertible to doomtype.h boolean") {
|
||||
REQUIRE(static_cast<boolean>(true) == 1);
|
||||
REQUIRE(static_cast<boolean>(false) == 0);
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#include "testbase.hpp"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
|
||||
namespace {
|
||||
class A {
|
||||
public:
|
||||
virtual bool foo() = 0;
|
||||
};
|
||||
class B : public A {
|
||||
public:
|
||||
virtual bool foo() override final { return true; };
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("NotNull<int*> is constructible from int*") {
|
||||
int a = 0;
|
||||
REQUIRE(srb2::NotNull(static_cast<int*>(&a)));
|
||||
}
|
||||
|
||||
TEST_CASE("NotNull<A*> is constructible from B* where B inherits from A") {
|
||||
B b;
|
||||
REQUIRE(srb2::NotNull(static_cast<B*>(&b)));
|
||||
}
|
||||
|
||||
TEST_CASE("NotNull<A*> dereferences to B& to call foo") {
|
||||
B b;
|
||||
srb2::NotNull<A*> a {static_cast<B*>(&b)};
|
||||
REQUIRE(a->foo());
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#ifndef __SRB2_TESTS_TESTBASE_HPP__
|
||||
#define __SRB2_TESTS_TESTBASE_HPP__
|
||||
|
||||
#define SRB2_ASSERT_HANDLER srb2::NoOpAssertHandler
|
||||
|
||||
#endif // __SRB2_TESTS_TESTBASE_HPP__
|
||||
Loading…
Reference in a new issue