mirror of https://gitlab.com/nakst/essence
2743 lines
64 KiB
C++
2743 lines
64 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
/////////////////////////////////
|
|
// EsRectangle utility functions.
|
|
/////////////////////////////////
|
|
|
|
#if defined(SHARED_COMMON_WANT_RECTANGLES) || defined(SHARED_COMMON_WANT_ALL)
|
|
|
|
struct Corners8 { int8_t tl, tr, bl, br; };
|
|
struct Corners32 { int32_t tl, tr, bl, br; };
|
|
struct Rectangle8 { int8_t l, r, t, b; };
|
|
struct Rectangle16 { int16_t l, r, t, b; };
|
|
struct Rectangle32 { int32_t l, r, t, b; };
|
|
|
|
#define RECT16_TO_RECT(x) ((EsRectangle) { (x).l, (x).r, (x).t, (x).b })
|
|
|
|
inline int Width(EsRectangle rectangle) {
|
|
return rectangle.r - rectangle.l;
|
|
}
|
|
|
|
inline int Height(EsRectangle rectangle) {
|
|
return rectangle.b - rectangle.t;
|
|
}
|
|
|
|
EsRectangle Translate(EsRectangle rectangle, int x, int y) {
|
|
rectangle.l += x, rectangle.r += x;
|
|
rectangle.t += y, rectangle.b += y;
|
|
return rectangle;
|
|
}
|
|
|
|
bool EsRectangleClip(EsRectangle parent, EsRectangle rectangle, EsRectangle *output) {
|
|
EsRectangle current = parent;
|
|
EsRectangle intersection;
|
|
|
|
if (!((current.l > rectangle.r && current.r > rectangle.l)
|
|
|| (current.t > rectangle.b && current.b > rectangle.t))) {
|
|
intersection.l = current.l > rectangle.l ? current.l : rectangle.l;
|
|
intersection.t = current.t > rectangle.t ? current.t : rectangle.t;
|
|
intersection.r = current.r < rectangle.r ? current.r : rectangle.r;
|
|
intersection.b = current.b < rectangle.b ? current.b : rectangle.b;
|
|
} else {
|
|
intersection = {};
|
|
}
|
|
|
|
if (output) {
|
|
*output = intersection;
|
|
}
|
|
|
|
return intersection.l < intersection.r && intersection.t < intersection.b;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
EsRectangle EsRectangleAddBorder(EsRectangle rectangle, EsRectangle border) {
|
|
rectangle.l += border.l;
|
|
rectangle.r -= border.r;
|
|
rectangle.t += border.t;
|
|
rectangle.b -= border.b;
|
|
return rectangle;
|
|
}
|
|
|
|
EsRectangle EsRectangleAdd(EsRectangle a, EsRectangle b) {
|
|
a.l += b.l;
|
|
a.t += b.t;
|
|
a.r += b.r;
|
|
a.b += b.b;
|
|
return a;
|
|
}
|
|
|
|
EsRectangle EsRectangleTranslate(EsRectangle a, EsRectangle b) {
|
|
a.l += b.l;
|
|
a.t += b.t;
|
|
a.r += b.l;
|
|
a.b += b.t;
|
|
return a;
|
|
}
|
|
|
|
EsRectangle EsRectangleSubtract(EsRectangle a, EsRectangle b) {
|
|
a.l -= b.l;
|
|
a.t -= b.t;
|
|
a.r -= b.r;
|
|
a.b -= b.b;
|
|
return a;
|
|
}
|
|
|
|
EsRectangle EsRectangleBounding(EsRectangle a, EsRectangle b) {
|
|
if (a.l > b.l) a.l = b.l;
|
|
if (a.t > b.t) a.t = b.t;
|
|
if (a.r < b.r) a.r = b.r;
|
|
if (a.b < b.b) a.b = b.b;
|
|
return a;
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
EsRectangle EsRectangleIntersection(EsRectangle a, EsRectangle b) {
|
|
if (a.l < b.l) a.l = b.l;
|
|
if (a.t < b.t) a.t = b.t;
|
|
if (a.r > b.r) a.r = b.r;
|
|
if (a.b > b.b) a.b = b.b;
|
|
return a;
|
|
}
|
|
|
|
EsRectangle EsRectangleCenter(EsRectangle parent, EsRectangle child) {
|
|
int childWidth = Width(child), childHeight = Height(child);
|
|
int parentWidth = Width(parent), parentHeight = Height(parent);
|
|
child.l = parentWidth / 2 - childWidth / 2 + parent.l, child.r = child.l + childWidth;
|
|
child.t = parentHeight / 2 - childHeight / 2 + parent.t, child.b = child.t + childHeight;
|
|
return child;
|
|
}
|
|
|
|
EsRectangle EsRectangleFit(EsRectangle parent, EsRectangle child, bool allowScalingUp) {
|
|
int childWidth = Width(child), childHeight = Height(child);
|
|
int parentWidth = Width(parent), parentHeight = Height(parent);
|
|
|
|
if (childWidth < parentWidth && childHeight < parentHeight && !allowScalingUp) {
|
|
return EsRectangleCenter(parent, child);
|
|
}
|
|
|
|
float childAspectRatio = (float) childWidth / childHeight;
|
|
int childMaximumWidth = parentHeight * childAspectRatio;
|
|
int childMaximumHeight = parentWidth / childAspectRatio;
|
|
|
|
if (childMaximumWidth > parentWidth) {
|
|
return EsRectangleCenter(parent, ES_RECT_2S(parentWidth, childMaximumHeight));
|
|
} else {
|
|
return EsRectangleCenter(parent, ES_RECT_2S(childMaximumWidth, parentHeight));
|
|
}
|
|
}
|
|
|
|
bool EsRectangleEquals(EsRectangle a, EsRectangle b) {
|
|
return a.l == b.l && a.r == b.r && a.t == b.t && a.b == b.b;
|
|
}
|
|
|
|
bool EsRectangleContains(EsRectangle a, int32_t x, int32_t y) {
|
|
return ES_RECT_VALID(a) && a.l <= x && a.r > x && a.t <= y && a.b > y;
|
|
}
|
|
|
|
bool EsRectangleContainsAll(EsRectangle parent, EsRectangle child) {
|
|
return ES_RECT_VALID(parent) && child.l >= parent.l && child.r <= parent.r && child.t >= parent.t && child.b <= parent.b;
|
|
}
|
|
|
|
EsRectangle EsRectangleSplit(EsRectangle *a, int32_t amount, char side, int32_t gap) {
|
|
EsRectangle b = *a;
|
|
if (side == 'l') a->l += amount + gap, b.r = a->l - gap;
|
|
if (side == 'r') a->r -= amount + gap, b.l = a->r + gap;
|
|
if (side == 't') a->t += amount + gap, b.b = a->t - gap;
|
|
if (side == 'b') a->b -= amount + gap, b.t = a->b + gap;
|
|
return b;
|
|
}
|
|
|
|
EsRectangle EsRectangleCut(EsRectangle a, int32_t amount, char side) {
|
|
return EsRectangleSplit(&a, amount, side, 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Rendering.
|
|
/////////////////////////////////
|
|
|
|
#if defined(SHARED_COMMON_WANT_RENDERING) || defined(SHARED_COMMON_WANT_ALL)
|
|
|
|
ES_FUNCTION_OPTIMISE_O3
|
|
__attribute__((no_instrument_function))
|
|
void BlendPixel(uint32_t *destinationPixel, uint32_t modified, bool fullAlpha) {
|
|
if ((modified & 0xFF000000) == 0xFF000000) {
|
|
*destinationPixel = modified;
|
|
return;
|
|
} else if ((modified & 0xFF000000) == 0x00000000) {
|
|
return;
|
|
}
|
|
|
|
uint32_t m1, m2, a;
|
|
uint32_t original = *destinationPixel;
|
|
|
|
if ((*destinationPixel & 0xFF000000) != 0xFF000000 && fullAlpha) {
|
|
uint32_t alpha1 = (modified & 0xFF000000) >> 24;
|
|
uint32_t alpha2 = 255 - alpha1;
|
|
uint32_t alphaD = (original & 0xFF000000) >> 24;
|
|
uint32_t alphaD2 = alphaD * alpha2;
|
|
uint32_t alphaOut = alpha1 + (alphaD2 >> 8);
|
|
|
|
if (!alphaOut) {
|
|
return;
|
|
}
|
|
|
|
m2 = alphaD2 / alphaOut;
|
|
m1 = (alpha1 << 8) / alphaOut;
|
|
if (m2 == 0x100) m2--;
|
|
if (m1 == 0x100) m1--;
|
|
a = alphaOut << 24;
|
|
} else {
|
|
m1 = (modified & 0xFF000000) >> 24;
|
|
m2 = 255 - m1;
|
|
a = 0xFF000000;
|
|
}
|
|
|
|
uint32_t r2 = m2 * (original & 0x00FF00FF);
|
|
uint32_t g2 = m2 * (original & 0x0000FF00);
|
|
uint32_t r1 = m1 * (modified & 0x00FF00FF);
|
|
uint32_t g1 = m1 * (modified & 0x0000FF00);
|
|
uint32_t result = a | (0x0000FF00 & ((g1 + g2) >> 8)) | (0x00FF00FF & ((r1 + r2) >> 8));
|
|
*destinationPixel = result;
|
|
}
|
|
|
|
void _DrawBlock(uintptr_t stride, void *bits, EsRectangle bounds, uint32_t color, bool fullAlpha) {
|
|
stride /= 4;
|
|
uint32_t *lineStart = (uint32_t *) bits + bounds.t * stride + bounds.l;
|
|
|
|
__m128i color4 = _mm_set_epi32(color, color, color, color);
|
|
|
|
for (int i = 0; i < bounds.b - bounds.t; i++, lineStart += stride) {
|
|
uint32_t *destination = lineStart;
|
|
int j = bounds.r - bounds.l;
|
|
|
|
if ((color & 0xFF000000) != 0xFF000000) {
|
|
do {
|
|
BlendPixel(destination, color, fullAlpha);
|
|
destination++;
|
|
} while (--j);
|
|
} else {
|
|
while (j >= 4) {
|
|
_mm_storeu_si128((__m128i *) destination, color4);
|
|
destination += 4;
|
|
j -= 4;
|
|
}
|
|
|
|
while (j > 0) {
|
|
*destination = color;
|
|
destination++;
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
uint32_t EsColorBlend(uint32_t under, uint32_t over, bool fullAlpha) {
|
|
BlendPixel(&under, over, fullAlpha);
|
|
return under;
|
|
}
|
|
|
|
struct EsPaintTarget {
|
|
void *bits;
|
|
uint32_t width, height, stride;
|
|
bool fullAlpha, readOnly, fromBitmap, forWindowManager;
|
|
};
|
|
|
|
void EsDrawInvert(EsPainter *painter, EsRectangle bounds) {
|
|
EsPaintTarget *target = painter->target;
|
|
|
|
if (!EsRectangleClip(bounds, painter->clip, &bounds)) {
|
|
return;
|
|
}
|
|
|
|
uintptr_t stride = target->stride / 4;
|
|
uint32_t *lineStart = (uint32_t *) target->bits + bounds.t * stride + bounds.l;
|
|
|
|
__m128i mask = _mm_set_epi32(0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF);
|
|
|
|
for (int i = 0; i < bounds.b - bounds.t; i++, lineStart += stride) {
|
|
uint32_t *destination = lineStart;
|
|
int j = bounds.r - bounds.l;
|
|
|
|
while (j >= 4) {
|
|
_mm_storeu_si128((__m128i *) destination, _mm_xor_si128(_mm_loadu_si128((__m128i *) destination), mask));
|
|
destination += 4;
|
|
j -= 4;
|
|
}
|
|
|
|
while (j > 0) {
|
|
*destination ^= 0xFFFFFF;
|
|
destination++;
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EsDrawClear(EsPainter *painter, EsRectangle bounds) {
|
|
EsPaintTarget *target = painter->target;
|
|
|
|
if (!EsRectangleClip(bounds, painter->clip, &bounds)) {
|
|
return;
|
|
}
|
|
|
|
uintptr_t stride = target->stride / 4;
|
|
uint32_t *lineStart = (uint32_t *) target->bits + bounds.t * stride + bounds.l;
|
|
|
|
__m128i zero = {};
|
|
|
|
for (int i = 0; i < bounds.b - bounds.t; i++, lineStart += stride) {
|
|
uint32_t *destination = lineStart;
|
|
int j = bounds.r - bounds.l;
|
|
|
|
while (j >= 4) {
|
|
_mm_storeu_si128((__m128i *) destination, zero);
|
|
destination += 4;
|
|
j -= 4;
|
|
}
|
|
|
|
while (j > 0) {
|
|
*destination = 0;
|
|
destination++;
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EsDrawBlock(EsPainter *painter, EsRectangle bounds, uint32_t color) {
|
|
if (!(color & 0xFF000000)) {
|
|
return;
|
|
}
|
|
|
|
EsPaintTarget *target = painter->target;
|
|
|
|
if (!EsRectangleClip(bounds, painter->clip, &bounds)) {
|
|
return;
|
|
}
|
|
|
|
_DrawBlock(target->stride, target->bits, bounds, color, target->fullAlpha);
|
|
}
|
|
|
|
void EsDrawBitmap(EsPainter *painter, EsRectangle region, const uint32_t *sourceBits, uintptr_t sourceStride, uint16_t mode) {
|
|
EsPaintTarget *target = painter->target;
|
|
EsRectangle bounds;
|
|
|
|
if (!EsRectangleClip(region, painter->clip, &bounds)) {
|
|
return;
|
|
}
|
|
|
|
sourceStride /= 4;
|
|
uintptr_t stride = target->stride / 4;
|
|
uint32_t *lineStart = (uint32_t *) target->bits + bounds.t * stride + bounds.l;
|
|
const uint32_t *sourceLineStart = sourceBits + (bounds.l - region.l) + sourceStride * (bounds.t - region.t);
|
|
|
|
for (int i = 0; i < bounds.b - bounds.t; i++, lineStart += stride, sourceLineStart += sourceStride) {
|
|
uint32_t *destination = lineStart;
|
|
const uint32_t *source = sourceLineStart;
|
|
int j = bounds.r - bounds.l;
|
|
|
|
if (mode == 0xFF) {
|
|
do {
|
|
BlendPixel(destination, *source, target->fullAlpha);
|
|
destination++;
|
|
source++;
|
|
} while (--j);
|
|
} else if (mode <= 0xFF) {
|
|
do {
|
|
uint32_t modified = *source;
|
|
modified = (modified & 0xFFFFFF) | (((((modified & 0xFF000000) >> 24) * mode) << 16) & 0xFF000000);
|
|
BlendPixel(destination, modified, target->fullAlpha);
|
|
destination++;
|
|
source++;
|
|
} while (--j);
|
|
} else if (mode == ES_DRAW_BITMAP_XOR) {
|
|
while (j >= 4) {
|
|
__m128i *_destination = (__m128i *) destination;
|
|
_mm_storeu_si128(_destination, _mm_xor_si128(_mm_loadu_si128((__m128i *) source), _mm_loadu_si128(_destination)));
|
|
destination += 4;
|
|
source += 4;
|
|
j -= 4;
|
|
}
|
|
|
|
while (j > 0) {
|
|
*destination ^= *source;
|
|
destination++;
|
|
source++;
|
|
j--;
|
|
}
|
|
} else if (mode == ES_DRAW_BITMAP_OPAQUE) {
|
|
__m128i fillAlpha = _mm_set1_epi32(0xFF000000);
|
|
|
|
while (j >= 4) {
|
|
_mm_storeu_si128((__m128i *) destination, _mm_or_si128(fillAlpha, _mm_loadu_si128((__m128i *) source)));
|
|
destination += 4;
|
|
source += 4;
|
|
j -= 4;
|
|
}
|
|
|
|
while (j > 0) {
|
|
*destination = 0xFF000000 | *source;
|
|
destination++;
|
|
source++;
|
|
j--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EsDrawRectangle(EsPainter *painter, EsRectangle r, uint32_t mainColor, uint32_t borderColor, EsRectangle borderSize) {
|
|
EsDrawBlock(painter, ES_RECT_4(r.l, r.r, r.t, r.t + borderSize.t), borderColor);
|
|
EsDrawBlock(painter, ES_RECT_4(r.l, r.l + borderSize.l, r.t + borderSize.t, r.b - borderSize.b), borderColor);
|
|
EsDrawBlock(painter, ES_RECT_4(r.r - borderSize.r, r.r, r.t + borderSize.t, r.b - borderSize.b), borderColor);
|
|
EsDrawBlock(painter, ES_RECT_4(r.l, r.r, r.b - borderSize.b, r.b), borderColor);
|
|
EsDrawBlock(painter, ES_RECT_4(r.l + borderSize.l, r.r - borderSize.r, r.t + borderSize.t, r.b - borderSize.b), mainColor);
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
void EsDrawBitmapScaled(EsPainter *painter, EsRectangle destinationRegion, EsRectangle sourceRegion, const uint32_t *sourceBits, uintptr_t sourceStride, uint16_t alpha) {
|
|
EsRectangle bounds = EsRectangleIntersection(painter->clip, destinationRegion);
|
|
uint32_t *destinationBits = (uint32_t *) painter->target->bits;
|
|
uintptr_t destinationStride = painter->target->stride;
|
|
bool fullAlpha = painter->target->fullAlpha;
|
|
|
|
for (int32_t y = bounds.t; y < bounds.b; y++) {
|
|
int32_t sy = LinearMap(destinationRegion.t, destinationRegion.b, sourceRegion.t, sourceRegion.b, y);
|
|
float sxDelta = (float) (sourceRegion.l - sourceRegion.r) / (destinationRegion.l - destinationRegion.r);
|
|
float sxFloat = LinearMap(destinationRegion.l, destinationRegion.r, sourceRegion.l, sourceRegion.r, bounds.l);
|
|
|
|
for (int32_t x = bounds.l; x < bounds.r; x++) {
|
|
int32_t sx = sxFloat;
|
|
sxFloat += sxDelta;
|
|
|
|
uint32_t *destinationPixel = destinationBits + x + y * destinationStride / 4;
|
|
const uint32_t *sourcePixel = sourceBits + sx + sy * sourceStride / 4;
|
|
uint32_t modified = *sourcePixel;
|
|
|
|
if (alpha == ES_DRAW_BITMAP_OPAQUE) {
|
|
*destinationPixel = modified;
|
|
} else {
|
|
if (alpha != 0xFF) {
|
|
modified = (modified & 0xFFFFFF) | (((((modified & 0xFF000000) >> 24) * alpha) << 16) & 0xFF000000);
|
|
}
|
|
|
|
BlendPixel(destinationPixel, modified, fullAlpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EsDrawPaintTarget(EsPainter *painter, EsPaintTarget *source, EsRectangle destinationRegion, EsRectangle sourceRegion, uint8_t alpha) {
|
|
bool scale = !(Width(destinationRegion) == Width(sourceRegion) && Height(destinationRegion) == Height(sourceRegion));
|
|
|
|
if (scale) {
|
|
EsDrawBitmapScaled(painter, destinationRegion,
|
|
sourceRegion, (uint32_t *) source->bits,
|
|
source->stride, source->fullAlpha ? alpha : 0xFFFF);
|
|
} else {
|
|
EsDrawBitmap(painter, destinationRegion,
|
|
(uint32_t *) source->bits + sourceRegion.l + sourceRegion.t * source->stride / 4,
|
|
source->stride, source->fullAlpha ? alpha : 0xFFFF);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// String utility functions.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
size_t EsCStringLength(const char *string) {
|
|
if (!string) {
|
|
return 0;
|
|
}
|
|
|
|
size_t size = 0;
|
|
|
|
while (true) {
|
|
if (*string) {
|
|
size++;
|
|
string++;
|
|
} else {
|
|
return size;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef void (*FormatCallback)(int character, void *data);
|
|
|
|
void _FormatInteger(FormatCallback callback, void *callbackData, long value, int pad = 0, bool simple = false) {
|
|
char buffer[32];
|
|
|
|
if (value < 0) {
|
|
callback('-', callbackData);
|
|
} else if (value == 0) {
|
|
for (int i = 0; i < (pad ?: 1); i++) {
|
|
callback('0', callbackData);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int bp = 0;
|
|
|
|
while (value) {
|
|
int digit = (value % 10);
|
|
if (digit < 0) digit = -digit;
|
|
buffer[bp++] = '0' + digit;
|
|
value /= 10;
|
|
}
|
|
|
|
int cr = bp % 3;
|
|
|
|
for (int i = 0; i < pad - bp; i++) {
|
|
callback('0', callbackData);
|
|
}
|
|
|
|
for (int i = bp - 1; i >= 0; i--, cr--) {
|
|
if (!cr && !pad) {
|
|
if (i != bp - 1 && !simple) callback(',', callbackData);
|
|
cr = 3;
|
|
}
|
|
|
|
callback(buffer[i], callbackData);
|
|
}
|
|
}
|
|
|
|
void WriteCStringToCallback(FormatCallback callback, void *callbackData, const char *cString) {
|
|
while (cString && *cString) {
|
|
callback(utf8_value(cString), callbackData);
|
|
cString = utf8_advance(cString);
|
|
}
|
|
}
|
|
|
|
void _StringFormat(FormatCallback callback, void *callbackData, const char *format, va_list arguments) {
|
|
int c;
|
|
int pad = 0;
|
|
int decimalPlaces = -1;
|
|
uint32_t flags = 0;
|
|
|
|
char buffer[32];
|
|
const char *hexChars = "0123456789ABCDEF";
|
|
|
|
while ((c = utf8_value((char *) format))) {
|
|
if (c == '%') {
|
|
repeat:;
|
|
format = utf8_advance((char *) format);
|
|
c = utf8_value((char *) format);
|
|
|
|
switch (c) {
|
|
case 'd': {
|
|
long value = va_arg(arguments, long);
|
|
_FormatInteger(callback, callbackData, value, pad, flags & ES_STRING_FORMAT_SIMPLE);
|
|
} break;
|
|
|
|
case 'i': {
|
|
int value = va_arg(arguments, int);
|
|
_FormatInteger(callback, callbackData, value, pad, flags & ES_STRING_FORMAT_SIMPLE);
|
|
} break;
|
|
|
|
case 'D': {
|
|
long value = va_arg(arguments, long);
|
|
|
|
if (value == 0) {
|
|
WriteCStringToCallback(callback, callbackData, interfaceString_CommonEmpty);
|
|
} else if (value < 1000) {
|
|
_FormatInteger(callback, callbackData, value, pad);
|
|
WriteCStringToCallback(callback, callbackData, interfaceString_CommonUnitBytes);
|
|
} else if (value < 1000000) {
|
|
_FormatInteger(callback, callbackData, value / 1000, pad);
|
|
callback('.', callbackData);
|
|
_FormatInteger(callback, callbackData, (value / 100) % 10, pad);
|
|
WriteCStringToCallback(callback, callbackData, interfaceString_CommonUnitKilobytes);
|
|
} else if (value < 1000000000) {
|
|
_FormatInteger(callback, callbackData, value / 1000000, pad);
|
|
callback('.', callbackData);
|
|
_FormatInteger(callback, callbackData, (value / 100000) % 10, pad);
|
|
WriteCStringToCallback(callback, callbackData, interfaceString_CommonUnitMegabytes);
|
|
} else {
|
|
_FormatInteger(callback, callbackData, value / 1000000000, pad);
|
|
callback('.', callbackData);
|
|
_FormatInteger(callback, callbackData, (value / 100000000) % 10, pad);
|
|
WriteCStringToCallback(callback, callbackData, interfaceString_CommonUnitGigabytes);
|
|
}
|
|
} break;
|
|
|
|
case 'R': {
|
|
EsRectangle value = va_arg(arguments, EsRectangle);
|
|
callback('{', callbackData);
|
|
_FormatInteger(callback, callbackData, value.l);
|
|
callback('-', callbackData);
|
|
callback('>', callbackData);
|
|
_FormatInteger(callback, callbackData, value.r);
|
|
callback(';', callbackData);
|
|
_FormatInteger(callback, callbackData, value.t);
|
|
callback('-', callbackData);
|
|
callback('>', callbackData);
|
|
_FormatInteger(callback, callbackData, value.b);
|
|
callback('}', callbackData);
|
|
} break;
|
|
|
|
case 'I': {
|
|
EsUniqueIdentifier value = va_arg(arguments, EsUniqueIdentifier);
|
|
|
|
for (uintptr_t i = 0; i < 16; i++) {
|
|
if (i) callback('-', callbackData);
|
|
callback(hexChars[(value.d[i] & 0xF0) >> 4], callbackData);
|
|
callback(hexChars[(value.d[i] & 0xF)], callbackData);
|
|
}
|
|
} break;
|
|
|
|
case 'X': {
|
|
uintptr_t value = va_arg(arguments, uintptr_t);
|
|
callback(hexChars[(value & 0xF0) >> 4], callbackData);
|
|
callback(hexChars[(value & 0xF)], callbackData);
|
|
} break;
|
|
|
|
case 'W': {
|
|
uintptr_t value = va_arg(arguments, uintptr_t);
|
|
callback(hexChars[(value & 0xF000) >> 12], callbackData);
|
|
callback(hexChars[(value & 0xF00) >> 8], callbackData);
|
|
callback(hexChars[(value & 0xF0) >> 4], callbackData);
|
|
callback(hexChars[(value & 0xF)], callbackData);
|
|
} break;
|
|
|
|
case 'x': {
|
|
uintptr_t value = va_arg(arguments, uintptr_t);
|
|
bool simple = flags & ES_STRING_FORMAT_SIMPLE;
|
|
if (!simple) callback('0', callbackData);
|
|
if (!simple) callback('x', callbackData);
|
|
int bp = 0;
|
|
while (value) {
|
|
buffer[bp++] = hexChars[value % 16];
|
|
value /= 16;
|
|
}
|
|
int j = 0, k = 0;
|
|
for (int i = 0; i < 16 - bp; i++) {
|
|
callback('0', callbackData);
|
|
j++;k++;if (k != 16 && j == 4 && !simple) { callback('_',callbackData); } j&=3;
|
|
}
|
|
for (int i = bp - 1; i >= 0; i--) {
|
|
callback(buffer[i], callbackData);
|
|
j++;k++;if (k != 16 && j == 4 && !simple) { callback('_',callbackData); } j&=3;
|
|
}
|
|
} break;
|
|
|
|
case 'c': {
|
|
callback(va_arg(arguments, int), callbackData);
|
|
} break;
|
|
|
|
case '%': {
|
|
callback('%', callbackData);
|
|
} break;
|
|
|
|
case 's': {
|
|
ptrdiff_t length = va_arg(arguments, ptrdiff_t);
|
|
char *string = va_arg(arguments, char *);
|
|
|
|
if (length == -1) {
|
|
WriteCStringToCallback(callback, callbackData, string ?: "[null]");
|
|
} else {
|
|
char *position = string;
|
|
|
|
while (position < string + length) {
|
|
callback(utf8_value(position), callbackData);
|
|
position = utf8_advance(position);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case 'z': {
|
|
const char *string = va_arg(arguments, const char *);
|
|
if (!string) string = "[null]";
|
|
WriteCStringToCallback(callback, callbackData, string);
|
|
} break;
|
|
|
|
case 'F': {
|
|
double number = va_arg(arguments, double);
|
|
|
|
if (__builtin_isnan(number)) {
|
|
WriteCStringToCallback(callback, callbackData, "NaN");
|
|
break;
|
|
} else if (__builtin_isinf(number)) {
|
|
if (number < 0) callback('-', callbackData);
|
|
WriteCStringToCallback(callback, callbackData, "inf");
|
|
break;
|
|
}
|
|
|
|
if (number < 0) {
|
|
callback('-', callbackData);
|
|
number = -number;
|
|
}
|
|
|
|
int digits[32];
|
|
size_t digitCount = 0;
|
|
const size_t maximumDigits = 12;
|
|
|
|
int64_t integer = number;
|
|
number -= integer;
|
|
// number is now in the range [0,1).
|
|
|
|
while (number && digitCount <= maximumDigits) {
|
|
// Extract the fractional digits.
|
|
number *= 10;
|
|
int digit = number;
|
|
number -= digit;
|
|
digits[digitCount++] = digit;
|
|
}
|
|
|
|
if (digitCount > maximumDigits) {
|
|
if (digits[maximumDigits] >= 5) {
|
|
// Round up.
|
|
for (intptr_t i = digitCount - 2; i >= -1; i--) {
|
|
if (i == -1) {
|
|
integer++;
|
|
} else {
|
|
digits[i]++;
|
|
|
|
if (digits[i] == 10) {
|
|
digits[i] = 0;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hide the last digit.
|
|
digitCount = maximumDigits;
|
|
}
|
|
|
|
// Trim trailing zeroes.
|
|
while (digitCount) {
|
|
if (!digits[digitCount - 1]) {
|
|
digitCount--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Integer digits.
|
|
_FormatInteger(callback, callbackData, integer, pad, flags & ES_STRING_FORMAT_SIMPLE);
|
|
|
|
// Decimal separator.
|
|
if (digitCount && decimalPlaces) {
|
|
callback('.', callbackData);
|
|
}
|
|
|
|
// Fractional digits.
|
|
for (uintptr_t i = 0; i < digitCount; i++) {
|
|
if ((int) i == decimalPlaces) break;
|
|
callback('0' + digits[i], callbackData);
|
|
}
|
|
|
|
decimalPlaces = -1;
|
|
} break;
|
|
|
|
case '*': {
|
|
pad = va_arg(arguments, int);
|
|
goto repeat;
|
|
} break;
|
|
|
|
case '.': {
|
|
decimalPlaces = va_arg(arguments, int);
|
|
goto repeat;
|
|
} break;
|
|
|
|
case 'f': {
|
|
flags = va_arg(arguments, uint32_t);
|
|
goto repeat;
|
|
} break;
|
|
}
|
|
|
|
pad = 0;
|
|
flags = 0;
|
|
} else {
|
|
callback(c, callbackData);
|
|
}
|
|
|
|
format = utf8_advance((char *) format);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
char *buffer;
|
|
size_t bytesRemaining, bytesWritten;
|
|
bool full;
|
|
} EsStringFormatInformation;
|
|
|
|
void StringFormatCallback(int character, void *_fsi) {
|
|
EsStringFormatInformation *fsi = (EsStringFormatInformation *) _fsi;
|
|
|
|
if (fsi->full) {
|
|
return;
|
|
}
|
|
|
|
char data[4];
|
|
size_t bytes = utf8_encode(character, data);
|
|
|
|
if (fsi->buffer) {
|
|
if (fsi->bytesRemaining < bytes && fsi->bytesRemaining != (size_t) ES_STRING_FORMAT_ENOUGH_SPACE) {
|
|
fsi->full = true;
|
|
return;
|
|
} else {
|
|
utf8_encode(character, fsi->buffer);
|
|
fsi->buffer += bytes;
|
|
fsi->bytesWritten += bytes;
|
|
if (fsi->bytesRemaining != (size_t) ES_STRING_FORMAT_ENOUGH_SPACE) fsi->bytesRemaining -= bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
ptrdiff_t EsStringFormat(char *buffer, size_t bufferLength, const char *format, ...) {
|
|
EsStringFormatInformation fsi = {buffer, bufferLength, 0};
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
_StringFormat(StringFormatCallback, &fsi, format, arguments);
|
|
va_end(arguments);
|
|
return fsi.bytesWritten;
|
|
}
|
|
|
|
ptrdiff_t EsStringFormatV(char *buffer, size_t bufferLength, const char *format, va_list arguments) {
|
|
EsStringFormatInformation fsi = {buffer, bufferLength, 0};
|
|
_StringFormat(StringFormatCallback, &fsi, format, arguments);
|
|
return fsi.bytesWritten;
|
|
}
|
|
|
|
void StringFormatCallback2(int character, void *_buffer) {
|
|
EsBuffer *buffer = (EsBuffer *) _buffer;
|
|
char data[4];
|
|
size_t bytes = utf8_encode(character, data);
|
|
EsBufferWrite(buffer, data, bytes);
|
|
}
|
|
|
|
void EsBufferFormat(EsBuffer *buffer, EsCString format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
if (!buffer->error) _StringFormat(StringFormatCallback2, buffer, format, arguments);
|
|
va_end(arguments);
|
|
}
|
|
|
|
void EsBufferFormatV(EsBuffer *buffer, EsCString format, va_list arguments) {
|
|
if (!buffer->error) _StringFormat(StringFormatCallback2, buffer, format, arguments);
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
char *EsStringAllocateAndFormatV(size_t *bytes, const char *format, va_list arguments1) {
|
|
size_t needed = 0;
|
|
|
|
va_list arguments2;
|
|
va_copy(arguments2, arguments1);
|
|
|
|
_StringFormat([] (int character, void *data) {
|
|
size_t *needed = (size_t *) data;
|
|
*needed = *needed + utf8_encode(character, nullptr);
|
|
}, &needed, format, arguments1);
|
|
|
|
if (bytes) *bytes = needed;
|
|
char *buffer = (char *) EsHeapAllocate(needed + 1, false);
|
|
|
|
if (!buffer) {
|
|
if (bytes) *bytes = 0;
|
|
return nullptr;
|
|
}
|
|
|
|
char *position = buffer;
|
|
buffer[needed] = 0;
|
|
|
|
_StringFormat([] (int character, void *data) {
|
|
char **position = (char **) data;
|
|
*position = *position + utf8_encode(character, *position);
|
|
}, &position, format, arguments2);
|
|
|
|
va_end(arguments2);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
char *EsStringAllocateAndFormat(size_t *bytes, const char *format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
char *buffer = EsStringAllocateAndFormatV(bytes, format, arguments);
|
|
va_end(arguments);
|
|
return buffer;
|
|
}
|
|
|
|
const char *EsStringFormatTemporary(const char *format, ...) {
|
|
EsMessageMutexCheck();
|
|
static char *buffer = nullptr;
|
|
EsHeapFree(buffer);
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
size_t bytes = 0;
|
|
buffer = EsStringAllocateAndFormatV(&bytes, format, arguments);
|
|
va_end(arguments);
|
|
return buffer;
|
|
}
|
|
#endif
|
|
|
|
int64_t EsStringParseInteger(const char **string, size_t *length, int base) {
|
|
int64_t value = 0;
|
|
bool overflow = false;
|
|
|
|
while (*length) {
|
|
char c = (*string)[0];
|
|
|
|
int64_t digit = 0;
|
|
|
|
if (c >= 'a' && c <= 'z') {
|
|
digit = c - 'a' + 10;
|
|
} else if (c >= 'A' && c <= 'Z') {
|
|
digit = c - 'A' + 10;
|
|
} else if (c >= '0' && c <= '9') {
|
|
digit = c - '0';
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (digit >= base) {
|
|
break;
|
|
}
|
|
|
|
int64_t oldValue = value;
|
|
|
|
value *= base;
|
|
value += digit;
|
|
|
|
if (value / base != oldValue) {
|
|
overflow = true;
|
|
}
|
|
|
|
(*string)++;
|
|
(*length)--;
|
|
}
|
|
|
|
if (overflow) value = LONG_MAX;
|
|
return value;
|
|
}
|
|
|
|
int EsStringCompareRaw(const char *s1, ptrdiff_t length1, const char *s2, ptrdiff_t length2) {
|
|
if (length1 == -1) length1 = EsCStringLength(s1);
|
|
if (length2 == -1) length2 = EsCStringLength(s2);
|
|
if (s1 == s2 && length1 == length2) return 0;
|
|
|
|
while (length1 || length2) {
|
|
if (!length1) return -1;
|
|
if (!length2) return 1;
|
|
|
|
char c1 = *s1;
|
|
char c2 = *s2;
|
|
|
|
if (c1 != c2) {
|
|
return c1 - c2;
|
|
}
|
|
|
|
length1--;
|
|
length2--;
|
|
s1++;
|
|
s2++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int EsStringCompare(const char *s1, ptrdiff_t _length1, const char *s2, ptrdiff_t _length2) {
|
|
if (_length1 == -1) _length1 = EsCStringLength(s1);
|
|
if (_length2 == -1) _length2 = EsCStringLength(s2);
|
|
size_t length1 = _length1, length2 = _length2;
|
|
if (s1 == s2 && length1 == length2) return 0;
|
|
|
|
while (length1 || length2) {
|
|
if (!length1) return -1;
|
|
if (!length2) return 1;
|
|
|
|
// Skip over rich text markup.
|
|
if (*s1 == '\a') while (length1 && *s1 != ']') s1++, length1--;
|
|
if (*s2 == '\a') while (length2 && *s2 != ']') s2++, length2--;
|
|
|
|
char c1 = *s1;
|
|
char c2 = *s2;
|
|
|
|
if (c1 >= '0' && c1 <= '9' && c2 >= '0' && c2 <= '9') {
|
|
int64_t n1 = EsStringParseInteger(&s1, &length1, 10);
|
|
int64_t n2 = EsStringParseInteger(&s2, &length2, 10);
|
|
|
|
if (n1 != n2) {
|
|
if (n1 > n2) return 1;
|
|
if (n1 < n2) return -1;
|
|
}
|
|
} else {
|
|
if (c1 >= 'a' && c1 <= 'z') c1 = c1 - 'a' + 'A';
|
|
if (c2 >= 'a' && c2 <= 'z') c2 = c2 - 'a' + 'A';
|
|
if (c1 == '.') c1 = ' '; else if (c1 == ' ') c1 = '.';
|
|
if (c2 == '.') c2 = ' '; else if (c2 == ' ') c2 = '.';
|
|
|
|
if (c1 != c2) {
|
|
if (c1 > c2) return 1;
|
|
if (c1 < c2) return -1;
|
|
}
|
|
|
|
length1--;
|
|
length2--;
|
|
s1++;
|
|
s2++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool StringStartsWith(const char *string, intptr_t _stringBytes, const char *prefix, intptr_t _prefixBytes, bool caseInsensitive) {
|
|
if (_stringBytes == -1) _stringBytes = EsCStringLength(string);
|
|
if (_prefixBytes == -1) _prefixBytes = EsCStringLength(prefix);
|
|
size_t stringBytes = _stringBytes, prefixBytes = _prefixBytes;
|
|
|
|
while (true) {
|
|
if (!prefixBytes) return true;
|
|
if (!stringBytes) return false;
|
|
|
|
char c1 = *string;
|
|
char c2 = *prefix;
|
|
|
|
if (caseInsensitive) {
|
|
if (c1 >= 'a' && c1 <= 'z') c1 = c1 - 'a' + 'A';
|
|
if (c2 >= 'a' && c2 <= 'z') c2 = c2 - 'a' + 'A';
|
|
}
|
|
|
|
if (c1 != c2) return false;
|
|
|
|
stringBytes--;
|
|
prefixBytes--;
|
|
string++;
|
|
prefix++;
|
|
}
|
|
}
|
|
|
|
uint32_t EsColorParse(const char *string, ptrdiff_t bytes) {
|
|
if (bytes == -1) {
|
|
bytes = EsCStringLength(string);
|
|
}
|
|
|
|
int digits[8], digitCount = 0;
|
|
ptrdiff_t position = 0;
|
|
|
|
while (position != bytes && !EsCRTisxdigit(string[position])) {
|
|
position++;
|
|
}
|
|
|
|
for (int i = 0; i < 8 && position != bytes; i++) {
|
|
char c = string[position++];
|
|
|
|
if (EsCRTisxdigit(c)) {
|
|
digits[digitCount++] = EsCRTisdigit(c) ? (c - '0') : EsCRTisupper(c) ? (c - 'A' + 10) : (c - 'a' + 10);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t color = 0;
|
|
|
|
if (digitCount == 3) {
|
|
color = 0xFF000000 | (digits[0] << 20) | (digits[0] << 16) | (digits[1] << 12) | (digits[1] << 8) | (digits[2] << 4) | (digits[2] << 0);
|
|
} else if (digitCount == 4) {
|
|
color = (digits[0] << 28 | digits[0] << 24) | (digits[1] << 20) | (digits[1] << 16)
|
|
| (digits[2] << 12) | (digits[2] << 8) | (digits[3] << 4) | (digits[3] << 0);
|
|
} else if (digitCount == 5) {
|
|
color = (digits[0] << 28 | digits[1] << 24) | (digits[2] << 20) | (digits[2] << 16)
|
|
| (digits[3] << 12) | (digits[3] << 8) | (digits[4] << 4) | (digits[4] << 0);
|
|
} else if (digitCount == 6) {
|
|
color = 0xFF000000 | (digits[0] << 20) | (digits[1] << 16) | (digits[2] << 12) | (digits[3] << 8) | (digits[4] << 4) | (digits[5] << 0);
|
|
} else if (digitCount == 8) {
|
|
color = (digits[0] << 28) | (digits[1] << 24) | (digits[2] << 20) | (digits[3] << 16)
|
|
| (digits[4] << 12) | (digits[5] << 8) | (digits[6] << 4) | (digits[7] << 0);
|
|
}
|
|
|
|
return color;
|
|
}
|
|
|
|
static int64_t ConvertCharacterToDigit(int character, int base) {
|
|
int64_t result = -1;
|
|
|
|
if (character >= '0' && character <= '9') {
|
|
result = character - '0';
|
|
} else if (character >= 'A' && character <= 'Z') {
|
|
result = character - 'A' + 10;
|
|
} else if (character >= 'a' && character <= 'z') {
|
|
result = character - 'a' + 10;
|
|
}
|
|
|
|
if (result >= base) {
|
|
result = -1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
EsUniqueIdentifier EsUniqueIdentifierParse(const char *text, ptrdiff_t bytes) {
|
|
if (bytes == -1) bytes = EsCStringLength(text);
|
|
if (bytes != 3 * 16 - 1) return {};
|
|
|
|
for (uintptr_t i = 0; i < 15; i++) {
|
|
if (text[i * 3 + 2] != '-') {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
EsUniqueIdentifier identifier;
|
|
|
|
for (uintptr_t i = 0; i < 16; i++) {
|
|
int64_t a = ConvertCharacterToDigit(text[i * 3 + 0], 16);
|
|
int64_t b = ConvertCharacterToDigit(text[i * 3 + 1], 16);
|
|
if (a == -1 || b == -1) return {};
|
|
identifier.d[i] = a * 16 + b;
|
|
}
|
|
|
|
return identifier;
|
|
}
|
|
|
|
int64_t EsIntegerParse(const char *text, ptrdiff_t bytes) {
|
|
if (bytes == -1) bytes = EsCStringLength(text);
|
|
|
|
int base = 10;
|
|
bool seenFirstRecognisedCharacter = false;
|
|
|
|
if (bytes > 2 && text[0] == '0' && text[1] == 'x') {
|
|
text += 2, bytes -= 2;
|
|
base = 16;
|
|
seenFirstRecognisedCharacter = true;
|
|
}
|
|
|
|
const char *end = text + bytes;
|
|
|
|
bool negative = false;
|
|
int64_t result = 0;
|
|
|
|
while (text < end) {
|
|
char c = *text;
|
|
|
|
if (c == '-' && !seenFirstRecognisedCharacter) {
|
|
negative = true;
|
|
seenFirstRecognisedCharacter = true;
|
|
} else if (c >= '0' && c <= '9') {
|
|
result *= base;
|
|
result += c - '0';
|
|
seenFirstRecognisedCharacter = true;
|
|
} else if (c >= 'A' && c <= 'F' && base == 16) {
|
|
result *= base;
|
|
result += c - 'A' + 10;
|
|
seenFirstRecognisedCharacter = true;
|
|
} else if (c >= 'a' && c <= 'f' && base == 16) {
|
|
result *= base;
|
|
result += c - 'a' + 10;
|
|
seenFirstRecognisedCharacter = true;
|
|
}
|
|
|
|
text++;
|
|
}
|
|
|
|
return negative ? -result : result;
|
|
}
|
|
|
|
bool EsStringFormatAppendV(char *buffer, size_t bufferLength, size_t *bufferPosition, const char *format, va_list arguments) {
|
|
buffer += *bufferPosition;
|
|
bufferLength -= *bufferPosition;
|
|
EsStringFormatInformation fsi = {buffer, bufferLength, 0};
|
|
_StringFormat(StringFormatCallback, &fsi, format, arguments);
|
|
*bufferPosition += fsi.bytesWritten;
|
|
return !fsi.full;
|
|
}
|
|
|
|
bool EsStringFormatAppend(char *buffer, size_t bufferLength, size_t *bufferPosition, const char *format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
bool result = EsStringFormatAppendV(buffer, bufferLength, bufferPosition, format, arguments);
|
|
va_end(arguments);
|
|
return result;
|
|
}
|
|
|
|
double EsDoubleParse(const char *nptr, ptrdiff_t maxBytes, char **endptr) {
|
|
if (maxBytes == -1) maxBytes = EsCStringLength(nptr);
|
|
const char *end = nptr + maxBytes;
|
|
if (nptr == end) return 0;
|
|
|
|
while (nptr != end && EsCRTisspace(*nptr)) {
|
|
nptr++;
|
|
}
|
|
|
|
if (nptr == end) return 0;
|
|
|
|
bool positive = true;
|
|
|
|
if (*nptr == '+') {
|
|
positive = true;
|
|
nptr++;
|
|
} else if (*nptr == '-') {
|
|
positive = false;
|
|
nptr++;
|
|
}
|
|
|
|
if (nptr == end) return 0;
|
|
|
|
double value = 0, scale = 0.1;
|
|
bool seenDecimalPoint = false;
|
|
|
|
while (nptr != end) {
|
|
char c = *nptr;
|
|
|
|
if (c == '.' && !seenDecimalPoint) {
|
|
seenDecimalPoint = true;
|
|
} else if (c >= '0' && c <= '9') {
|
|
if (seenDecimalPoint) {
|
|
value += scale * (c - '0');
|
|
scale *= 0.1;
|
|
} else {
|
|
value = value * 10;
|
|
value += c - '0';
|
|
}
|
|
} else if (c == ',') {
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
nptr++;
|
|
}
|
|
|
|
if (!positive) {
|
|
value = -value;
|
|
}
|
|
|
|
if (endptr) *endptr = (char *) nptr;
|
|
return value;
|
|
}
|
|
|
|
void PathGetName(const char *path, ptrdiff_t pathBytes, const char **name, ptrdiff_t *nameBytes) {
|
|
if (pathBytes == -1) {
|
|
pathBytes = EsCStringLength(path);
|
|
}
|
|
|
|
*name = path;
|
|
*nameBytes = pathBytes;
|
|
|
|
for (uintptr_t i = pathBytes; i > 0; i--) {
|
|
if (path[i - 1] == '/') {
|
|
*name = path + i;
|
|
*nameBytes = pathBytes - i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Memory utility functions.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsMemoryCopy(void *_destination, const void *_source, size_t bytes) {
|
|
// TODO Prevent this from being optimised out in the kernel.
|
|
|
|
if (!bytes) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *destination = (uint8_t *) _destination;
|
|
uint8_t *source = (uint8_t *) _source;
|
|
|
|
#ifdef ES_ARCH_X86_64
|
|
while (bytes >= 16) {
|
|
_mm_storeu_si128((__m128i *) destination,
|
|
_mm_loadu_si128((__m128i *) source));
|
|
|
|
source += 16;
|
|
destination += 16;
|
|
bytes -= 16;
|
|
}
|
|
#endif
|
|
|
|
while (bytes >= 1) {
|
|
((uint8_t *) destination)[0] = ((uint8_t *) source)[0];
|
|
|
|
source += 1;
|
|
destination += 1;
|
|
bytes -= 1;
|
|
}
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsMemoryCopyReverse(void *_destination, const void *_source, size_t bytes) {
|
|
// TODO Prevent this from being optimised out in the kernel.
|
|
|
|
if (!bytes) {
|
|
return;
|
|
}
|
|
|
|
uint8_t *destination = (uint8_t *) _destination;
|
|
uint8_t *source = (uint8_t *) _source;
|
|
|
|
destination += bytes - 1;
|
|
source += bytes - 1;
|
|
|
|
while (bytes >= 1) {
|
|
((uint8_t *) destination)[0] = ((uint8_t *) source)[0];
|
|
|
|
source -= 1;
|
|
destination -= 1;
|
|
bytes -= 1;
|
|
}
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsMemoryZero(void *destination, size_t bytes) {
|
|
// TODO Prevent this from being optimised out in the kernel.
|
|
|
|
if (!bytes) {
|
|
return;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < bytes; i++) {
|
|
((uint8_t *) destination)[i] = 0;
|
|
}
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsMemoryMove(void *_start, void *_end, intptr_t amount, bool zeroEmptySpace) {
|
|
// TODO Prevent this from being optimised out in the kernel.
|
|
|
|
uint8_t *start = (uint8_t *) _start;
|
|
uint8_t *end = (uint8_t *) _end;
|
|
|
|
if (end < start) {
|
|
EsPanic("MemoryMove end < start: %x %x %x %d\n", start, end, amount, zeroEmptySpace);
|
|
return;
|
|
}
|
|
|
|
if (amount > 0) {
|
|
EsMemoryCopyReverse(start + amount, start, end - start);
|
|
|
|
if (zeroEmptySpace) {
|
|
EsMemoryZero(start, amount);
|
|
}
|
|
} else if (amount < 0) {
|
|
EsMemoryCopy(start + amount, start, end - start);
|
|
|
|
if (zeroEmptySpace) {
|
|
EsMemoryZero(end + amount, -amount);
|
|
}
|
|
}
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
int EsMemoryCompare(const void *a, const void *b, size_t bytes) {
|
|
if (!bytes) {
|
|
return 0;
|
|
}
|
|
|
|
const uint8_t *x = (const uint8_t *) a;
|
|
const uint8_t *y = (const uint8_t *) b;
|
|
|
|
for (uintptr_t i = 0; i < bytes; i++) {
|
|
if (x[i] < y[i]) {
|
|
return -1;
|
|
} else if (x[i] > y[i]) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
uint8_t EsMemorySumBytes(uint8_t *source, size_t bytes) {
|
|
if (!bytes) {
|
|
return 0;
|
|
}
|
|
|
|
uint8_t total = 0;
|
|
|
|
for (uintptr_t i = 0; i < bytes; i++) {
|
|
total += source[i];
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsMemoryFill(void *from, void *to, uint8_t byte) {
|
|
uint8_t *a = (uint8_t *) from;
|
|
uint8_t *b = (uint8_t *) to;
|
|
while (a != b) *a = byte, a++;
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Printing.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
#ifdef USE_STB_SPRINTF
|
|
#define STB_SPRINTF_IMPLEMENTATION
|
|
#include "stb_sprintf.h"
|
|
#endif
|
|
|
|
#ifndef KERNEL
|
|
|
|
static struct {
|
|
EsMutex mutex;
|
|
|
|
#define PRINT_BUFFER_SIZE (1024)
|
|
char buffer[PRINT_BUFFER_SIZE];
|
|
uintptr_t bufferPosition;
|
|
} printing;
|
|
|
|
void PrintCallback(int character, void *) {
|
|
if (printing.bufferPosition >= PRINT_BUFFER_SIZE - 16) {
|
|
EsSyscall(ES_SYSCALL_PRINT, (uintptr_t) printing.buffer, printing.bufferPosition, 0, 0);
|
|
printing.bufferPosition = 0;
|
|
}
|
|
|
|
printing.bufferPosition += utf8_encode(character, printing.buffer + printing.bufferPosition);
|
|
}
|
|
|
|
void EsPrint(const char *format, ...) {
|
|
EsMutexAcquire(&printing.mutex);
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
_StringFormat(PrintCallback, nullptr, format, arguments);
|
|
va_end(arguments);
|
|
EsSyscall(ES_SYSCALL_PRINT, (uintptr_t) printing.buffer, printing.bufferPosition, 0, 0);
|
|
printing.bufferPosition = 0;
|
|
EsMutexRelease(&printing.mutex);
|
|
}
|
|
|
|
void EsPrintDirect(const char *string, ptrdiff_t stringLength) {
|
|
if (stringLength == -1) stringLength = EsCStringLength(string);
|
|
EsSyscall(ES_SYSCALL_PRINT, (uintptr_t) string, stringLength, 0, 0);
|
|
}
|
|
|
|
void EsPrintHelloWorld() {
|
|
EsPrint("Hello, world.\n");
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Random number generator.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
struct RNGState {
|
|
uint64_t s[4];
|
|
EsSpinlock lock;
|
|
};
|
|
|
|
RNGState rngState;
|
|
|
|
void EsRandomAddEntropy(uint64_t x) {
|
|
EsSpinlockAcquire(&rngState.lock);
|
|
|
|
for (uintptr_t i = 0; i < 4; i++) {
|
|
x += 0x9E3779B97F4A7C15;
|
|
|
|
uint64_t result = x;
|
|
result = (result ^ (result >> 30)) * 0xBF58476D1CE4E5B9;
|
|
result = (result ^ (result >> 27)) * 0x94D049BB133111EB;
|
|
rngState.s[i] ^= result ^ (result >> 31);
|
|
}
|
|
|
|
EsSpinlockRelease(&rngState.lock);
|
|
}
|
|
|
|
void EsRandomSeed(uint64_t x) {
|
|
EsSpinlockAcquire(&rngState.lock);
|
|
|
|
rngState.s[0] = rngState.s[1] = rngState.s[2] = rngState.s[3] = 0;
|
|
|
|
for (uintptr_t i = 0; i < 4; i++) {
|
|
x += 0x9E3779B97F4A7C15;
|
|
|
|
uint64_t result = x;
|
|
result = (result ^ (result >> 30)) * 0xBF58476D1CE4E5B9;
|
|
result = (result ^ (result >> 27)) * 0x94D049BB133111EB;
|
|
rngState.s[i] = result ^ (result >> 31);
|
|
}
|
|
|
|
EsSpinlockRelease(&rngState.lock);
|
|
}
|
|
|
|
uint64_t EsRandomU64() {
|
|
EsSpinlockAcquire(&rngState.lock);
|
|
|
|
uint64_t result = rngState.s[1] * 5;
|
|
result = ((result << 7) | (result >> 57)) * 9;
|
|
|
|
uint64_t t = rngState.s[1] << 17;
|
|
rngState.s[2] ^= rngState.s[0];
|
|
rngState.s[3] ^= rngState.s[1];
|
|
rngState.s[1] ^= rngState.s[2];
|
|
rngState.s[0] ^= rngState.s[3];
|
|
rngState.s[2] ^= t;
|
|
rngState.s[3] = (rngState.s[3] << 45) | (rngState.s[3] >> 19);
|
|
|
|
EsSpinlockRelease(&rngState.lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
uint8_t EsRandomU8() {
|
|
return (uint8_t) EsRandomU64();
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Standard algorithms.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
void EsSortWithSwapCallback(void *_base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, EsGeneric),
|
|
EsGeneric argument, void (*swap)(const void *, const void *, EsGeneric)) {
|
|
if (nmemb <= 1) return;
|
|
uint8_t *base = (uint8_t *) _base;
|
|
intptr_t i = -1, j = nmemb;
|
|
|
|
while (true) {
|
|
while (compar(base + ++i * size, base, argument) < 0);
|
|
while (compar(base + --j * size, base, argument) > 0);
|
|
if (i >= j) break;
|
|
swap(base + i * size, base + j * size, argument);
|
|
}
|
|
|
|
EsSortWithSwapCallback(base, ++j, size, compar, argument, swap);
|
|
EsSortWithSwapCallback(base + j * size, nmemb - j, size, compar, argument, swap);
|
|
}
|
|
|
|
void EsSort(void *_base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, EsGeneric), EsGeneric argument) {
|
|
if (nmemb <= 1) return;
|
|
|
|
uint8_t *base = (uint8_t *) _base;
|
|
uint8_t *swap = (uint8_t *) __builtin_alloca(size);
|
|
|
|
intptr_t i = -1, j = nmemb;
|
|
|
|
while (true) {
|
|
while (compar(base + ++i * size, base, argument) < 0);
|
|
while (compar(base + --j * size, base, argument) > 0);
|
|
|
|
if (i >= j) break;
|
|
|
|
EsMemoryCopy(swap, base + i * size, size);
|
|
EsMemoryCopy(base + i * size, base + j * size, size);
|
|
EsMemoryCopy(base + j * size, swap, size);
|
|
}
|
|
|
|
EsSort(base, ++j, size, compar, argument);
|
|
EsSort(base + j * size, nmemb - j, size, compar, argument);
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Miscellaneous.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
void EnterDebugger() {
|
|
// Do nothing.
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
size_t EsPathFindUniqueName(char *buffer, size_t originalBytes, size_t bufferBytes) {
|
|
if (originalBytes && buffer[originalBytes - 1] == '/') {
|
|
originalBytes--;
|
|
}
|
|
|
|
size_t extensionPoint = originalBytes;
|
|
|
|
for (uintptr_t i = 0; i < originalBytes; i++) {
|
|
if (buffer[i] == '.') {
|
|
extensionPoint = i;
|
|
} else if (buffer[i] == '/') {
|
|
extensionPoint = originalBytes;
|
|
}
|
|
}
|
|
|
|
if (!EsPathExists(buffer, originalBytes)) {
|
|
return originalBytes;
|
|
}
|
|
|
|
char *buffer2 = (char *) EsHeapAllocate(bufferBytes, false);
|
|
|
|
if (!buffer2) {
|
|
return 0;
|
|
}
|
|
|
|
EsDefer(EsHeapFree(buffer2));
|
|
|
|
uintptr_t attempt = 2;
|
|
|
|
// TODO Check that this runs in a reasonable amount of time when all files are already present.
|
|
|
|
while (attempt < 1000) {
|
|
size_t length = EsStringFormat(buffer2, bufferBytes, "%s %d%s", extensionPoint, buffer,
|
|
attempt, originalBytes - extensionPoint, buffer + extensionPoint);
|
|
|
|
if (!EsPathExists(buffer2, length)) {
|
|
EsMemoryCopy(buffer, buffer2, length);
|
|
return length;
|
|
} else {
|
|
attempt++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t *EsImageLoad(const void *file, size_t fileSize, uint32_t *imageX, uint32_t *imageY, int imageChannels) {
|
|
#ifdef USE_STB_IMAGE
|
|
int unused;
|
|
uint32_t *image = (uint32_t *) stbi_load_from_memory((uint8_t *) file, fileSize, (int *) imageX, (int *) imageY, &unused, imageChannels);
|
|
|
|
if (!image) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (uintptr_t j = 0; j < *imageY; j++) {
|
|
for (uintptr_t i = 0; i < *imageX; i++) {
|
|
uint32_t in = image[i + j * *imageX];
|
|
uint32_t red = ((in & 0xFF) << 16), green = (in & 0xFF00FF00), blue = ((in & 0xFF0000) >> 16);
|
|
image[i + j * *imageX] = red | green | blue;
|
|
}
|
|
}
|
|
|
|
return (uint8_t *) image;
|
|
#else
|
|
(void) imageChannels;
|
|
PNGReader reader = {};
|
|
reader.buffer = file;
|
|
reader.bytes = fileSize;
|
|
uint32_t *bits;
|
|
bool success = PNGParse(&reader, &bits, imageX, imageY, [] (size_t s) { return EsHeapAllocate(s, false); }, [] (void *p) { EsHeapFree(p); });
|
|
return success ? (uint8_t *) bits : nullptr;
|
|
#endif
|
|
}
|
|
|
|
void LoadImage(const void *path, ptrdiff_t pathBytes, void *destination, int destinationWidth, int destinationHeight,
|
|
bool fromMemory, bool *_coversDestination = nullptr) {
|
|
int width = 0, height = 0;
|
|
uint32_t *image = nullptr;
|
|
|
|
if (!fromMemory) {
|
|
size_t fileSize;
|
|
void *file = EsFileReadAll((const char *) path, pathBytes, &fileSize);
|
|
|
|
if (file) {
|
|
image = (uint32_t *) EsImageLoad((uint8_t *) file, fileSize, (uint32_t *) &width, (uint32_t *) &height, 4);
|
|
EsHeapFree(file);
|
|
}
|
|
} else {
|
|
image = (uint32_t *) EsImageLoad((uint8_t *) path, pathBytes, (uint32_t *) &width, (uint32_t *) &height, 4);
|
|
}
|
|
|
|
int cx = destinationWidth / 2 - width / 2, cy = destinationHeight / 2 - height / 2;
|
|
bool coversDestination = true;
|
|
|
|
for (int j = 0; j < destinationHeight; j++) {
|
|
uint32_t *pixel = (uint32_t *) ((uint8_t *) destination + j * destinationWidth * 4);
|
|
|
|
for (int i = 0; i < destinationWidth; i++, pixel++) {
|
|
if (i - cx >= 0 && i - cx < width && j - cy >= 0 && j - cy < height) {
|
|
*pixel = image[i - cx + (j - cy) * width];
|
|
} else {
|
|
coversDestination = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_coversDestination) *_coversDestination = coversDestination;
|
|
EsHeapFree(image);
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Synchronisation.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsSpinlockAcquire(EsSpinlock *spinlock) {
|
|
__sync_synchronize();
|
|
while (__sync_val_compare_and_swap(&spinlock->state, 0, 1));
|
|
__sync_synchronize();
|
|
}
|
|
|
|
__attribute__((no_instrument_function))
|
|
void EsSpinlockRelease(EsSpinlock *spinlock) {
|
|
__sync_synchronize();
|
|
|
|
if (!spinlock->state) {
|
|
EsPanic("EsSpinlockRelease - Spinlock %x not acquired.\n", spinlock);
|
|
}
|
|
|
|
spinlock->state = 0;
|
|
__sync_synchronize();
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
|
|
void EsMutexAcquire(EsMutex *mutex) {
|
|
bool acquired = false;
|
|
|
|
while (true) {
|
|
EsSpinlockAcquire(&mutex->spinlock);
|
|
|
|
if (mutex->event == ES_INVALID_HANDLE) {
|
|
mutex->event = EsEventCreate(false);
|
|
}
|
|
|
|
if (mutex->state == 0) {
|
|
acquired = true;
|
|
mutex->state = 1;
|
|
}
|
|
|
|
if (acquired) {
|
|
// TODO Test this.
|
|
EsSpinlockRelease(&mutex->spinlock);
|
|
return;
|
|
}
|
|
|
|
__sync_fetch_and_add(&mutex->queued, 1);
|
|
EsEventReset(mutex->event);
|
|
EsSpinlockRelease(&mutex->spinlock);
|
|
EsWaitSingle(mutex->event);
|
|
__sync_fetch_and_sub(&mutex->queued, 1);
|
|
}
|
|
}
|
|
|
|
void EsMutexRelease(EsMutex *mutex) {
|
|
volatile bool queued = false;
|
|
|
|
EsSpinlockAcquire(&mutex->spinlock);
|
|
|
|
if (!mutex->state) {
|
|
EsPanic("EsMutexRelease - Mutex not acquired.");
|
|
}
|
|
|
|
mutex->state = 0;
|
|
|
|
if (mutex->queued) {
|
|
queued = true;
|
|
EsEventSet(mutex->event);
|
|
}
|
|
|
|
EsSpinlockRelease(&mutex->spinlock);
|
|
|
|
if (queued) {
|
|
EsSchedulerYield();
|
|
}
|
|
}
|
|
|
|
void EsMutexDestroy(EsMutex *mutex) {
|
|
EsAssert(!mutex->state && !mutex->queued && !mutex->spinlock.state);
|
|
|
|
if (mutex->event != ES_INVALID_HANDLE) {
|
|
EsHandleClose(mutex->event);
|
|
mutex->event = ES_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Byte swapping.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
uint16_t ByteSwap16(uint16_t x) {
|
|
return (x << 8) | (x >> 8);
|
|
}
|
|
|
|
uint32_t ByteSwap32(uint32_t x) {
|
|
return ((x & 0xFF000000) >> 24)
|
|
| ((x & 0x000000FF) << 24)
|
|
| ((x & 0x00FF0000) >> 8)
|
|
| ((x & 0x0000FF00) << 8);
|
|
}
|
|
|
|
uint16_t SwapBigEndian16(uint16_t x) {
|
|
return ByteSwap16(x);
|
|
}
|
|
|
|
uint32_t SwapBigEndian32(uint32_t x) {
|
|
return ByteSwap32(x);
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// C standard library.
|
|
/////////////////////////////////
|
|
|
|
#ifdef SHARED_COMMON_WANT_ALL
|
|
|
|
void *EsCRTmemset(void *s, int c, size_t n) {
|
|
uint8_t *s8 = (uint8_t *) s;
|
|
for (uintptr_t i = 0; i < n; i++) {
|
|
s8[i] = (uint8_t) c;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void *EsCRTmemcpy(void *dest, const void *src, size_t n) {
|
|
uint8_t *dest8 = (uint8_t *) dest;
|
|
const uint8_t *src8 = (const uint8_t *) src;
|
|
for (uintptr_t i = 0; i < n; i++) {
|
|
dest8[i] = src8[i];
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
void *EsCRTmemmove(void *dest, const void *src, size_t n) {
|
|
if ((uintptr_t) dest < (uintptr_t) src) {
|
|
return EsCRTmemcpy(dest, src, n);
|
|
} else {
|
|
uint8_t *dest8 = (uint8_t *) dest;
|
|
const uint8_t *src8 = (const uint8_t *) src;
|
|
for (uintptr_t i = n; i; i--) {
|
|
dest8[i - 1] = src8[i - 1];
|
|
}
|
|
return dest;
|
|
}
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
char *EsCRTstrdup(const char *string) {
|
|
if (!string) return nullptr;
|
|
size_t length = EsCRTstrlen(string) + 1;
|
|
char *memory = (char *) EsCRTmalloc(length);
|
|
if (!memory) return nullptr;
|
|
EsCRTmemcpy(memory, string, length);
|
|
return memory;
|
|
}
|
|
#endif
|
|
|
|
size_t EsCRTstrlen(const char *s) {
|
|
size_t n = 0;
|
|
while (s[n]) n++;
|
|
return n;
|
|
}
|
|
|
|
size_t EsCRTstrnlen(const char *s, size_t maxlen) {
|
|
size_t n = 0;
|
|
while (s[n] && maxlen--) n++;
|
|
return n;
|
|
}
|
|
|
|
int EsCRTabs(int n) {
|
|
if (n < 0) return 0 - n;
|
|
else return n;
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
volatile static size_t mallocCount;
|
|
|
|
void *EsCRTmalloc(size_t size) {
|
|
void *x = EsHeapAllocate(size, false);
|
|
if (x) __sync_fetch_and_add(&mallocCount, 1);
|
|
return x;
|
|
}
|
|
|
|
void *EsCRTcalloc(size_t num, size_t size) {
|
|
size_t c;
|
|
if (__builtin_mul_overflow(num, size, &c)) return nullptr;
|
|
void *x = EsHeapAllocate(c, true);
|
|
if (x) __sync_fetch_and_add(&mallocCount, 1);
|
|
return x;
|
|
}
|
|
|
|
void EsCRTfree(void *ptr) {
|
|
if (ptr) __sync_fetch_and_sub(&mallocCount, 1);
|
|
EsHeapFree(ptr);
|
|
}
|
|
|
|
void *EsCRTrealloc(void *ptr, size_t size) {
|
|
// EsHeapReallocate handles this logic, but do it ourselves to keep mallocCount correct.
|
|
if (!ptr) return EsCRTmalloc(size);
|
|
else if (!size) return EsCRTfree(ptr), nullptr;
|
|
else return EsHeapReallocate(ptr, size, false);
|
|
}
|
|
#endif
|
|
|
|
char *EsCRTgetenv(const char *name) {
|
|
(void) name;
|
|
return nullptr;
|
|
}
|
|
|
|
int EsCRTtoupper(int c) {
|
|
if (c >= 'a' && c <= 'z') {
|
|
return c - 'a' + 'A';
|
|
} else {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
int EsCRTtolower(int c) {
|
|
if (c >= 'A' && c <= 'Z') {
|
|
return c - 'A' + 'a';
|
|
} else {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
int EsCRTstrcasecmp(const char *s1, const char *s2) {
|
|
while (true) {
|
|
if (*s1 != *s2 && EsCRTtolower(*s1) != EsCRTtolower(*s2)) {
|
|
if (*s1 == 0) return -1;
|
|
else if (*s2 == 0) return 1;
|
|
return *s1 - *s2;
|
|
}
|
|
|
|
if (*s1 == 0) {
|
|
return 0;
|
|
}
|
|
|
|
s1++;
|
|
s2++;
|
|
}
|
|
}
|
|
|
|
int EsCRTstrncasecmp(const char *s1, const char *s2, size_t n) {
|
|
while (n--) {
|
|
if (*s1 != *s2 && EsCRTtolower(*s1) != EsCRTtolower(*s2)) {
|
|
if (*s1 == 0) return -1;
|
|
else if (*s2 == 0) return 1;
|
|
return *s1 - *s2;
|
|
}
|
|
|
|
if (*s1 == 0) {
|
|
return 0;
|
|
}
|
|
|
|
s1++;
|
|
s2++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int EsCRTstrcmp(const char *s1, const char *s2) {
|
|
while (true) {
|
|
if (*s1 != *s2) {
|
|
if (*s1 == 0) return -1;
|
|
else if (*s2 == 0) return 1;
|
|
return *s1 - *s2;
|
|
}
|
|
|
|
if (*s1 == 0) {
|
|
return 0;
|
|
}
|
|
|
|
s1++;
|
|
s2++;
|
|
}
|
|
}
|
|
|
|
int EsCRTstrncmp(const char *s1, const char *s2, size_t n) {
|
|
while (n--) {
|
|
if (*s1 != *s2) {
|
|
if (*s1 == 0) return -1;
|
|
else if (*s2 == 0) return 1;
|
|
return *s1 - *s2;
|
|
}
|
|
|
|
if (*s1 == 0) {
|
|
return 0;
|
|
}
|
|
|
|
s1++;
|
|
s2++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int EsCRTisspace(int c) {
|
|
if (c == ' ') return 1;
|
|
if (c == '\f') return 1;
|
|
if (c == '\n') return 1;
|
|
if (c == '\r') return 1;
|
|
if (c == '\t') return 1;
|
|
if (c == '\v') return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t EsCRTstrtoul(const char *nptr, char **endptr, int base) {
|
|
// TODO errno
|
|
|
|
if (base > 36) return 0;
|
|
|
|
while (EsCRTisspace(*nptr)) {
|
|
nptr++;
|
|
}
|
|
|
|
if (*nptr == '+') {
|
|
nptr++;
|
|
} else if (*nptr == '-') {
|
|
nptr++;
|
|
}
|
|
|
|
if (base == 0) {
|
|
if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X')) {
|
|
base = 16;
|
|
nptr += 2;
|
|
} else if (nptr[0] == '0') {
|
|
EsPrint("WARNING: strtoul with base=0, detected octal\n");
|
|
base = 8; // Why?!?
|
|
nptr++;
|
|
} else {
|
|
base = 10;
|
|
}
|
|
}
|
|
|
|
uint64_t value = 0;
|
|
bool overflow = false;
|
|
|
|
while (true) {
|
|
int64_t digit = ConvertCharacterToDigit(*nptr, base);
|
|
|
|
if (digit != -1) {
|
|
nptr++;
|
|
|
|
uint64_t x = value;
|
|
value *= base;
|
|
value += (uint64_t) digit;
|
|
|
|
if (value / base != x) {
|
|
overflow = true;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (overflow) {
|
|
value = ULONG_MAX;
|
|
}
|
|
|
|
if (endptr) {
|
|
*endptr = (char *) nptr;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
float EsCRTstrtof(const char *nptr, char **endptr) {
|
|
return EsDoubleParse(nptr, -1, endptr);
|
|
}
|
|
|
|
double EsCRTstrtod(const char *nptr, char **endptr) {
|
|
return EsDoubleParse(nptr, -1, endptr);
|
|
}
|
|
|
|
float EsCRTatof(const char *nptr) {
|
|
return EsDoubleParse(nptr, -1, nullptr);
|
|
}
|
|
|
|
double EsCRTatod(const char *nptr) {
|
|
return EsDoubleParse(nptr, -1, nullptr);
|
|
}
|
|
|
|
size_t EsCRTstrcspn(const char *s, const char *reject) {
|
|
size_t count = 0;
|
|
|
|
while (true) {
|
|
char character = *s;
|
|
if (!character) return count;
|
|
|
|
const char *search = reject;
|
|
|
|
while (true) {
|
|
char c = *search;
|
|
|
|
if (!c) {
|
|
goto match;
|
|
} else if (character == c) {
|
|
break;
|
|
}
|
|
|
|
search++;
|
|
}
|
|
|
|
return count;
|
|
|
|
match:;
|
|
count++;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
char *EsCRTstrsep(char **stringp, const char *delim) {
|
|
char *string = *stringp;
|
|
|
|
if (!string) {
|
|
return NULL;
|
|
}
|
|
|
|
size_t tokenLength = EsCRTstrcspn(string, delim);
|
|
|
|
if (string[tokenLength] == 0) {
|
|
*stringp = NULL;
|
|
} else {
|
|
string[tokenLength] = 0;
|
|
*stringp = string + tokenLength + 1;
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
char *EsCRTstrcat(char *dest, const char *src) {
|
|
char *o = dest;
|
|
dest += EsCRTstrlen(dest);
|
|
|
|
while (*src) {
|
|
*dest = *src;
|
|
src++;
|
|
dest++;
|
|
}
|
|
|
|
*dest = 0;
|
|
|
|
return o;
|
|
}
|
|
|
|
long int EsCRTstrtol(const char *nptr, char **endptr, int base) {
|
|
// TODO errno
|
|
|
|
if (base > 36) return 0;
|
|
|
|
while (EsCRTisspace(*nptr)) {
|
|
nptr++;
|
|
}
|
|
|
|
bool positive = true;
|
|
|
|
if (*nptr == '+') {
|
|
positive = true;
|
|
nptr++;
|
|
} else if (*nptr == '-') {
|
|
positive = false;
|
|
nptr++;
|
|
}
|
|
|
|
if (base == 0) {
|
|
if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X')) {
|
|
base = 16;
|
|
nptr += 2;
|
|
} else if (nptr[0] == '0') {
|
|
EsPrint("WARNING: strtol with base=0, detected octal\n");
|
|
base = 8; // Why?!?
|
|
nptr++;
|
|
} else {
|
|
base = 10;
|
|
}
|
|
}
|
|
|
|
int64_t value = 0;
|
|
bool overflow = false;
|
|
|
|
while (true) {
|
|
int64_t digit = ConvertCharacterToDigit(*nptr, base);
|
|
|
|
if (digit != -1) {
|
|
nptr++;
|
|
|
|
int64_t x = value;
|
|
value *= base;
|
|
value += digit;
|
|
|
|
if (value / base != x) {
|
|
overflow = true;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!positive) {
|
|
value = -value;
|
|
}
|
|
|
|
if (overflow) {
|
|
value = positive ? LONG_MAX : LONG_MIN;
|
|
}
|
|
|
|
if (endptr) {
|
|
*endptr = (char *) nptr;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
int EsCRTatoi(const char *nptr) {
|
|
return (int) EsCRTstrtol(nptr, NULL, 10);
|
|
}
|
|
|
|
char *EsCRTstrstr(const char *haystack, const char *needle) {
|
|
size_t haystackLength = EsCRTstrlen(haystack);
|
|
size_t needleLength = EsCRTstrlen(needle);
|
|
|
|
if (haystackLength < needleLength) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i <= haystackLength - needleLength; i++) {
|
|
for (uintptr_t j = 0; j < needleLength; j++) {
|
|
if (haystack[i + j] != needle[j]) {
|
|
goto tryNext;
|
|
}
|
|
}
|
|
|
|
return (char *) haystack + i;
|
|
|
|
tryNext:;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void EsCRTqsort(void *_base, size_t nmemb, size_t size, EsCRTComparisonCallback compar) {
|
|
if (nmemb <= 1) return;
|
|
|
|
uint8_t *base = (uint8_t *) _base;
|
|
uint8_t *swap = (uint8_t *) __builtin_alloca(size);
|
|
|
|
intptr_t i = -1, j = nmemb;
|
|
|
|
while (true) {
|
|
while (compar(base + ++i * size, base) < 0);
|
|
while (compar(base + --j * size, base) > 0);
|
|
|
|
if (i >= j) break;
|
|
|
|
EsCRTmemcpy(swap, base + i * size, size);
|
|
EsCRTmemcpy(base + i * size, base + j * size, size);
|
|
EsCRTmemcpy(base + j * size, swap, size);
|
|
}
|
|
|
|
EsCRTqsort(base, ++j, size, compar);
|
|
EsCRTqsort(base + j * size, nmemb - j, size, compar);
|
|
}
|
|
|
|
void *EsCRTbsearch(const void *key, const void *base, size_t num, size_t size, EsCRTComparisonCallback compar) {
|
|
if (!num) return nullptr;
|
|
|
|
intptr_t low = 0;
|
|
intptr_t high = num - 1;
|
|
|
|
while (low <= high) {
|
|
uintptr_t index = ((high - low) >> 1) + low;
|
|
int result = compar(key, (uint8_t *) base + size * index);
|
|
|
|
if (result < 0) {
|
|
high = index - 1;
|
|
} else if (result > 0) {
|
|
low = index + 1;
|
|
} else {
|
|
return (uint8_t *) base + size * index;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
char *EsCRTstrcpy(char *dest, const char *src) {
|
|
size_t stringLength = EsCRTstrlen(src);
|
|
EsCRTmemcpy(dest, src, stringLength + 1);
|
|
return dest;
|
|
}
|
|
|
|
char *EsCRTstpcpy(char *dest, const char *src) {
|
|
size_t stringLength = EsCRTstrlen(src);
|
|
EsCRTmemcpy(dest, src, stringLength + 1);
|
|
return dest + stringLength;
|
|
}
|
|
|
|
size_t EsCRTstrspn(const char *s, const char *accept) {
|
|
size_t count = 0;
|
|
|
|
while (true) {
|
|
char character = *s;
|
|
|
|
const char *search = accept;
|
|
|
|
while (true) {
|
|
char c = *search;
|
|
|
|
if (!c) {
|
|
break;
|
|
} else if (character == c) {
|
|
goto match;
|
|
}
|
|
|
|
search++;
|
|
}
|
|
|
|
return count;
|
|
|
|
match:;
|
|
count++;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
char *EsCRTstrrchr(const char *s, int c) {
|
|
const char *start = s;
|
|
if (!s[0]) return NULL;
|
|
s += EsCRTstrlen(s) - 1;
|
|
|
|
while (true) {
|
|
if (*s == c) {
|
|
return (char *) s;
|
|
}
|
|
|
|
if (s == start) {
|
|
return NULL;
|
|
}
|
|
|
|
s--;
|
|
}
|
|
}
|
|
|
|
char *EsCRTstrchr(const char *s, int c) {
|
|
while (true) {
|
|
if (*s == c) {
|
|
return (char *) s;
|
|
}
|
|
|
|
if (*s == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
s++;
|
|
}
|
|
}
|
|
|
|
char *EsCRTstrncpy(char *dest, const char *src, size_t n) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < n && src[i]; i++) {
|
|
dest[i] = src[i];
|
|
}
|
|
|
|
for (; i < n; i++) {
|
|
dest[i] = 0;
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
char *EsCRTstrlcpy(char *dest, const char *src, size_t n) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < n - 1 && src[i]; i++) {
|
|
dest[i] = src[i];
|
|
}
|
|
|
|
for (; i < n; i++) {
|
|
dest[i] = 0;
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
int EsCRTmemcmp(const void *s1, const void *s2, size_t n) {
|
|
return EsMemoryCompare((void *) s1, (void *) s2, n);
|
|
}
|
|
|
|
void *EsCRTmemchr(const void *_s, int _c, size_t n) {
|
|
uint8_t *s = (uint8_t *) _s;
|
|
uint8_t c = (uint8_t) _c;
|
|
|
|
for (uintptr_t i = 0; i < n; i++) {
|
|
if (s[i] == c) {
|
|
return s + i;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int EsCRTisalpha(int c) {
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
|
}
|
|
|
|
int EsCRTisdigit(int c) {
|
|
return (c >= '0' && c <= '9');
|
|
}
|
|
|
|
int EsCRTrand() {
|
|
uint8_t a = EsRandomU8();
|
|
uint8_t b = EsRandomU8();
|
|
uint8_t c = EsRandomU8();
|
|
return (a << 16) | (b << 8) | (c << 0);
|
|
}
|
|
|
|
int EsCRTisalnum(int c) {
|
|
return EsCRTisalpha(c) || EsCRTisdigit(c);
|
|
}
|
|
|
|
int EsCRTiscntrl(int c) {
|
|
return c < 0x20 || c == 0x7F;
|
|
}
|
|
|
|
int EsCRTisgraph(int c) {
|
|
return c > ' ' && c < 0x7F;
|
|
}
|
|
|
|
int EsCRTislower(int c) {
|
|
return c >= 'a' && c <= 'z';
|
|
}
|
|
|
|
int EsCRTisprint(int c) {
|
|
return c >= ' ' && c < 127;
|
|
}
|
|
|
|
int EsCRTispunct(int c) {
|
|
return c != ' ' && !EsCRTisalnum(c);
|
|
}
|
|
|
|
int EsCRTisupper(int c) {
|
|
return c >= 'A' && c <= 'Z';
|
|
}
|
|
|
|
int EsCRTisxdigit(int c) {
|
|
return EsCRTisdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
|
}
|
|
|
|
char *EsCRTsetlocale(int category, const char *locale) {
|
|
(void) category;
|
|
(void) locale;
|
|
return nullptr;
|
|
}
|
|
|
|
void EsCRTsrand(unsigned int seed) {
|
|
EsRandomSeed(seed);
|
|
}
|
|
|
|
#ifndef KERNEL
|
|
void EsCRTexit(int status) {
|
|
EsProcessTerminate(ES_CURRENT_PROCESS, status);
|
|
}
|
|
|
|
void EsCRTabort() {
|
|
EsPanic("EsCRTabort called.\n");
|
|
}
|
|
#endif
|
|
|
|
int EsCRTstrcoll(const char *s1, const char *s2) {
|
|
return EsStringCompare(s1, EsCStringLength(s1), s2, EsCStringLength(s2));
|
|
}
|
|
|
|
char *EsCRTstrerror(int errnum) {
|
|
(void) errnum;
|
|
return (char*) "unknown operation failure";
|
|
}
|
|
|
|
char *EsCRTstrpbrk(const char *s, const char *accept) {
|
|
size_t l1 = EsCStringLength(s), l2 = EsCStringLength(accept);
|
|
|
|
for (uintptr_t i = 0; i < l1; i++) {
|
|
char c = s[i];
|
|
|
|
for (uintptr_t j = 0; j < l2; j++) {
|
|
if (accept[j] == c) {
|
|
return (char *) (i + s);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef USE_STB_SPRINTF
|
|
int EsCRTsprintf(char *buffer, const char *format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
int length = stbsp_vsprintf(buffer, format, arguments);
|
|
va_end(arguments);
|
|
return length;
|
|
}
|
|
|
|
int EsCRTsnprintf(char *buffer, size_t bufferSize, const char *format, ...) {
|
|
va_list arguments;
|
|
va_start(arguments, format);
|
|
int length = stbsp_vsnprintf(buffer, bufferSize, format, arguments);
|
|
va_end(arguments);
|
|
return length;
|
|
}
|
|
|
|
int EsCRTvsnprintf(char *buffer, size_t bufferSize, const char *format, va_list arguments) {
|
|
return stbsp_vsnprintf(buffer, bufferSize, format, arguments);
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Memory buffers.
|
|
/////////////////////////////////
|
|
|
|
#if defined(SHARED_COMMON_WANT_BUFFERS) || defined(SHARED_COMMON_WANT_ALL)
|
|
|
|
const void *EsBufferRead(EsBuffer *buffer, size_t readBytes) {
|
|
if (!readBytes && buffer->position == buffer->bytes) {
|
|
return buffer->in + buffer->position;
|
|
} else if (buffer->position >= buffer->bytes || buffer->bytes - buffer->position < readBytes || buffer->error) {
|
|
buffer->error = true;
|
|
return NULL;
|
|
} else {
|
|
const void *pointer = buffer->in + buffer->position;
|
|
buffer->position += readBytes;
|
|
return pointer;
|
|
}
|
|
}
|
|
|
|
bool EsBufferReadInto(EsBuffer *buffer, void *destination, size_t readBytes) {
|
|
// TODO Support buffered reading from a EsFileStore.
|
|
|
|
const void *source = EsBufferRead(buffer, readBytes);
|
|
|
|
if (source) {
|
|
EsMemoryCopy(destination, source, readBytes);
|
|
return true;
|
|
} else {
|
|
EsMemoryZero(destination, readBytes);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const void *EsBufferReadMany(EsBuffer *buffer, size_t a, size_t b) {
|
|
size_t c;
|
|
|
|
if (__builtin_mul_overflow(a, b, &c)) {
|
|
buffer->error = true;
|
|
return NULL;
|
|
} else {
|
|
return EsBufferRead(buffer, c);
|
|
}
|
|
}
|
|
|
|
float EsBufferReadFloat(EsBuffer *buffer) {
|
|
const float *p = (const float *) EsBufferRead(buffer, sizeof(float));
|
|
return p ? *p : 0;
|
|
}
|
|
|
|
uint8_t EsBufferReadByte(EsBuffer *buffer) {
|
|
const uint8_t *p = (const uint8_t *) EsBufferRead(buffer, sizeof(uint8_t));
|
|
return p ? *p : 0;
|
|
}
|
|
|
|
uint32_t EsBufferReadInt(EsBuffer *buffer) {
|
|
const uint32_t *p = (const uint32_t *) EsBufferRead(buffer, sizeof(uint32_t));
|
|
return p ? *p : 0;
|
|
}
|
|
|
|
#ifdef ES_API
|
|
void EsBufferFlushToFileStore(EsBuffer *buffer) {
|
|
if (!buffer->position || buffer->error) return;
|
|
EsAssert(buffer->fileStore && buffer->position <= buffer->bytes);
|
|
buffer->error = !EsFileStoreAppend(buffer->fileStore, buffer->out, buffer->position);
|
|
buffer->position = 0;
|
|
}
|
|
#endif
|
|
|
|
void *EsBufferWrite(EsBuffer *buffer, const void *source, size_t writeBytes) {
|
|
#ifdef ES_API
|
|
tryAgain:;
|
|
#endif
|
|
|
|
if (buffer->error) {
|
|
return NULL;
|
|
#ifdef ES_API
|
|
} else if (buffer->fileStore) {
|
|
if (writeBytes > buffer->bytes) {
|
|
EsBufferFlushToFileStore(buffer);
|
|
|
|
if (!buffer->error) {
|
|
buffer->error = !EsFileStoreAppend(buffer->fileStore, source, writeBytes);
|
|
}
|
|
} else {
|
|
while (writeBytes && !buffer->error) {
|
|
if (buffer->position == buffer->bytes) {
|
|
EsBufferFlushToFileStore(buffer);
|
|
} else {
|
|
size_t bytesToWrite = writeBytes > buffer->bytes - buffer->position ? buffer->bytes - buffer->position : writeBytes;
|
|
EsMemoryCopy(buffer->out + buffer->position, source, bytesToWrite);
|
|
buffer->position += bytesToWrite;
|
|
writeBytes -= bytesToWrite;
|
|
source = (const uint8_t *) source + bytesToWrite;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
#endif
|
|
} else if (buffer->bytes - buffer->position < writeBytes) {
|
|
#ifdef ES_API
|
|
if (buffer->canGrow) {
|
|
size_t newBytes = buffer->bytes * 2 + 16;
|
|
|
|
if (newBytes < writeBytes + buffer->position) {
|
|
newBytes = writeBytes + buffer->position + 16;
|
|
}
|
|
|
|
char *newOut = (char *) EsHeapReallocate(buffer->out, newBytes, false);
|
|
|
|
if (!newOut) {
|
|
buffer->error = true;
|
|
} else {
|
|
buffer->bytes = newBytes;
|
|
buffer->out = (uint8_t *) newOut;
|
|
goto tryAgain;
|
|
}
|
|
} else {
|
|
buffer->error = true;
|
|
}
|
|
#else
|
|
buffer->error = true;
|
|
#endif
|
|
|
|
return NULL;
|
|
} else {
|
|
void *pointer = buffer->out + buffer->position;
|
|
buffer->position += writeBytes;
|
|
|
|
if (source) {
|
|
EsMemoryCopy(pointer, source, writeBytes);
|
|
} else {
|
|
EsMemoryZero(pointer, writeBytes);
|
|
}
|
|
|
|
return pointer;
|
|
}
|
|
}
|
|
|
|
bool EsBufferWriteInt8(EsBuffer *buffer, int8_t value) {
|
|
EsBufferWrite(buffer, &value, sizeof(int8_t));
|
|
return buffer->error;
|
|
}
|
|
|
|
bool EsBufferWriteInt32Endian(EsBuffer *buffer, int32_t value) {
|
|
#ifdef __BIG_ENDIAN__
|
|
value = ByteSwap32(value);
|
|
#endif
|
|
EsBufferWrite(buffer, &value, sizeof(int32_t));
|
|
return buffer->error;
|
|
}
|
|
|
|
int32_t EsBufferReadInt32Endian(EsBuffer *buffer, int32_t errorValue) {
|
|
int32_t *pointer = (int32_t *) EsBufferRead(buffer, sizeof(int32_t));
|
|
if (!pointer) return errorValue;
|
|
#ifdef __BIG_ENDIAN__
|
|
return ByteSwap32(*pointer);
|
|
#else
|
|
return *pointer;
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
/////////////////////////////////
|
|
// Time and date.
|
|
/////////////////////////////////
|
|
|
|
#if defined(SHARED_COMMON_WANT_ALL)
|
|
|
|
const uint16_t daysBeforeMonthStart[] = {
|
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, // Normal year.
|
|
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, // Leap year.
|
|
};
|
|
|
|
uint64_t DateToLinear(const EsDateComponents *components) {
|
|
uint64_t dayCount = 365 * components->year + daysBeforeMonthStart[components->month - 1] + components->day - 1;
|
|
uint16_t year = components->month < 3 ? components->year - 1 : components->year;
|
|
dayCount += 1 + year / 4 - year / 100 + year / 400; // Add additional days for leap years, only including this year's if we're past February.
|
|
return components->millisecond + 1000 * (components->second + 60 * (components->minute + 60 * (components->hour + 24 * dayCount)));
|
|
}
|
|
|
|
uint8_t DateCalculateDayOfWeek(const EsDateComponents *components) {
|
|
return ((DateToLinear(components) / 86400000 + 5) % 7) + 1;
|
|
}
|
|
|
|
void DateToComponents(uint64_t x, EsDateComponents *components) {
|
|
components->_unused = 0;
|
|
|
|
components->millisecond = x % 1000, x /= 1000;
|
|
components->second = x % 60, x /= 60;
|
|
components->minute = x % 60, x /= 60;
|
|
components->hour = x % 24, x /= 24;
|
|
// x = days since epoch.
|
|
|
|
// 146097 days per 400 year block.
|
|
components->year = (x / 146097) * 400;
|
|
x %= 146097; // x = day within 400 year block.
|
|
|
|
bool isLeapYear = true;
|
|
|
|
// 36525 days in the first 100 year block, and 36524 per the other 100 year blocks.
|
|
if (x < 36525) {
|
|
components->year += (x / 1461) * 4;
|
|
x %= 1461; // x = day within 4 year block.
|
|
} else {
|
|
x -= 36525;
|
|
components->year += (x / 36524 + 1) * 100;
|
|
x %= 36524; // x = day within 100 year block.
|
|
|
|
// 1460 days in the first 4 year block, and 1461 per the other 4 year blocks.
|
|
if (x < 1460) components->year += x / 365, x %= 365, isLeapYear = false;
|
|
else components->year += ((x - 1460) / 1461 + 1) * 4, x = (x - 1460) % 1461;
|
|
}
|
|
|
|
if (x >= 366) components->year += (x - 1) / 365, x = (x - 1) % 365, isLeapYear = false;
|
|
// x = day within year.
|
|
|
|
for (uintptr_t i = 12; i >= 1; i--) {
|
|
uint16_t offset = daysBeforeMonthStart[i + (isLeapYear ? 11 : -1)];
|
|
|
|
if (x >= offset) {
|
|
components->month = i;
|
|
components->day = x - offset + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|