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
 |