essence-os/shared/common.cpp

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