// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using Newtonsoft.Json;
namespace FlaxEditor.Utilities
{
///
/// Editor utilities and helper functions.
///
public static class Utils
{
private static readonly string[] MemorySizePostfixes =
{
"B",
"kB",
"MB",
"GB",
"TB",
"PB"
};
internal enum VariantType
{
Null = 0,
Void,
Bool,
Int,
Uint,
Int64,
Uint64,
Float,
Double,
Pointer,
String,
Object,
Structure,
Asset,
Blob,
Enum,
Vector2,
Vector3,
Vector4,
Color,
Guid,
BoundingBox,
BoundingSphere,
Quaternion,
Transform,
Rectangle,
Ray,
Matrix,
Array,
Dictionary,
ManagedObject,
Typename,
}
///
/// The name of the Flax Engine C# assembly name.
///
public static readonly string FlaxEngineAssemblyName = "FlaxEngine.CSharp";
///
/// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB
///
/// The bytes.
/// The formatted amount of bytes.
public static string FormatBytesCount(int bytes)
{
int order = 0;
while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1)
{
order++;
bytes = bytes / 1024;
}
return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]);
}
///
/// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB
///
/// The bytes.
/// The formatted amount of bytes.
public static string FormatBytesCount(ulong bytes)
{
int order = 0;
while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1)
{
order++;
bytes = bytes / 1024;
}
return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]);
}
///
/// The colors for the keyframes used by the curve editor.
///
internal static readonly Color[] CurveKeyframesColors =
{
Color.OrangeRed,
Color.ForestGreen,
Color.CornflowerBlue,
Color.White,
};
///
/// The time/value axes tick steps for editors with timeline.
///
internal static readonly float[] CurveTickSteps =
{
0.0000001f, 0.0000005f, 0.000001f, 0.000005f, 0.00001f,
0.00005f, 0.0001f, 0.0005f, 0.001f, 0.005f,
0.01f, 0.05f, 0.1f, 0.5f, 1,
5, 10, 50, 100, 500,
1000, 5000, 10000, 50000, 100000,
500000, 1000000, 5000000, 10000000, 100000000
};
///
/// Determines whether the specified path string contains any invalid character.
///
/// The path.
/// true if the given string cannot be used as a path because it contains one or more illegal characters; otherwise, false.
public static bool HasInvalidPathChar(string path)
{
char[] illegalChars =
{
'?',
'\\',
'/',
'\"',
'<',
'>',
'|',
':',
'*',
'\u0001',
'\u0002',
'\u0003',
'\u0004',
'\u0005',
'\u0006',
'\a',
'\b',
'\t',
'\n',
'\v',
'\f',
'\r',
'\u000E',
'\u000F',
'\u0010',
'\u0011',
'\u0012',
'\u0013',
'\u0014',
'\u0015',
'\u0016',
'\u0017',
'\u0018',
'\u0019',
'\u001A',
'\u001B',
'\u001C',
'\u001D',
'\u001E',
'\u001F'
};
return path.IndexOfAny(illegalChars) != -1;
}
internal static Transform[] GetTransformsAndBounds(List nodes, out BoundingBox bounds)
{
var transforms = new Transform[nodes.Count];
bounds = BoundingBox.Empty;
for (int i = 0; i < nodes.Count; i++)
{
transforms[i] = nodes[i].Transform;
if (nodes[i] is ActorNode actorNode)
{
bounds = BoundingBox.Merge(bounds, actorNode.Actor.BoxWithChildren);
}
}
return transforms;
}
///
/// Removes the file if it exists.
///
/// The file path.
public static void RemoveFileIfExists(string file)
{
if (File.Exists(file))
File.Delete(file);
}
///
/// Copies the directory. Supports subdirectories copy with files override option.
///
/// The source directory path.
/// The destination directory path.
/// If set to true copy subdirectories.
/// if set to true override existing files.
public static void DirectoryCopy(string srcDirectoryPath, string dstDirectoryPath, bool copySubDirs = true, bool overrideFiles = false)
{
var dir = new DirectoryInfo(srcDirectoryPath);
if (!dir.Exists)
{
throw new DirectoryNotFoundException("Missing source directory to copy. " + srcDirectoryPath);
}
if (!Directory.Exists(dstDirectoryPath))
{
Directory.CreateDirectory(dstDirectoryPath);
}
var files = dir.GetFiles();
for (int i = 0; i < files.Length; i++)
{
string tmp = Path.Combine(dstDirectoryPath, files[i].Name);
files[i].CopyTo(tmp, overrideFiles);
}
if (copySubDirs)
{
var dirs = dir.GetDirectories();
for (int i = 0; i < dirs.Length; i++)
{
string tmp = Path.Combine(dstDirectoryPath, dirs[i].Name);
DirectoryCopy(dirs[i].FullName, tmp, true, overrideFiles);
}
}
}
///
/// Converts the raw bytes into the structure. Supported only for structures with simple types and no GC objects.
///
/// The structure type.
/// The data bytes.
/// The structure.
public static T ByteArrayToStructure(byte[] bytes) where T : struct
{
// #stupid c#
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return stuff;
}
///
/// Converts the raw bytes into the structure. Supported only for structures with simple types and no GC objects.
///
/// The data bytes.
/// The structure type.
/// The structure.
public static object ByteArrayToStructure(byte[] bytes, Type type)
{
// #stupid c#
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
object stuff = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), type);
handle.Free();
return stuff;
}
///
/// Converts the raw bytes into the structure. Supported only for structures with simple types and no GC objects.
///
/// The structure type.
/// The data bytes.
/// The result.
public static void ByteArrayToStructure(byte[] bytes, out T result) where T : struct
{
// #stupid c#
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
result = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
}
///
/// Converts the structure to the raw bytes. Supported only for structures with simple types and no GC objects.
///
/// The structure type.
/// The structure value.
/// The bytes array that contains a structure data.
public static byte[] StructureToByteArray(ref T value) where T : struct
{
// #stupid c#
int size = Marshal.SizeOf(typeof(T));
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(value, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
///
/// Converts the structure to the raw bytes. Supported only for structures with simple types and no GC objects.
///
/// The structure value.
/// The structure type.
/// The bytes array that contains a structure data.
public static byte[] StructureToByteArray(object value, Type type)
{
// #stupid c#
int size = Marshal.SizeOf(type);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(value, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
internal static unsafe string ReadStr(this BinaryReader stream, int check)
{
int length = stream.ReadInt32();
if (length > 0 && length < 4 * 1024)
{
var str = stream.ReadBytes(length * 2);
fixed (byte* strPtr = str)
{
var ptr = (char*)strPtr;
for (int j = 0; j < length; j++)
ptr[j] = (char)(ptr[j] ^ check);
}
return System.Text.Encoding.Unicode.GetString(str);
}
return string.Empty;
}
internal static unsafe string ReadStrAnsi(this BinaryReader stream, int check)
{
int length = stream.ReadInt32();
if (length > 0 && length < 4 * 1024)
{
var str = stream.ReadBytes(length);
fixed (byte* strPtr = str)
{
var ptr = strPtr;
for (int j = 0; j < length; j++)
ptr[j] = (byte)(ptr[j] ^ check);
}
return System.Text.Encoding.ASCII.GetString(str);
}
return string.Empty;
}
internal static unsafe void WriteStr(this BinaryWriter stream, string str, int check)
{
int length = str?.Length ?? 0;
stream.Write(length);
if (length == 0)
return;
var bytes = System.Text.Encoding.Unicode.GetBytes(str);
if (bytes.Length != length * 2)
throw new ArgumentException();
fixed (byte* bytesPtr = bytes)
{
var ptr = (char*)bytesPtr;
for (int j = 0; j < length; j++)
ptr[j] = (char)(ptr[j] ^ check);
}
stream.Write(bytes);
}
internal static unsafe void WriteStrAnsi(this BinaryWriter stream, string str, int check)
{
int length = str?.Length ?? 0;
stream.Write(length);
if (length == 0)
return;
var bytes = System.Text.Encoding.ASCII.GetBytes(str);
if (bytes.Length != length)
throw new ArgumentException();
fixed (byte* bytesPtr = bytes)
{
var ptr = bytesPtr;
for (int j = 0; j < length; j++)
ptr[j] = (byte)(ptr[j] ^ check);
}
stream.Write(bytes);
}
internal static VariantType ToVariantType(this Type type)
{
VariantType variantType;
if (type == null)
variantType = VariantType.Null;
else if (type == typeof(void))
variantType = VariantType.Void;
else if (type == typeof(bool))
variantType = VariantType.Bool;
else if (type == typeof(int))
variantType = VariantType.Int;
else if (type == typeof(uint))
variantType = VariantType.Uint;
else if (type == typeof(long))
variantType = VariantType.Int64;
else if (type == typeof(ulong))
variantType = VariantType.Uint64;
else if (type.IsEnum)
variantType = VariantType.Enum;
else if (type == typeof(float))
variantType = VariantType.Float;
else if (type == typeof(double))
variantType = VariantType.Double;
else if (type == typeof(IntPtr))
variantType = VariantType.Pointer;
else if (type == typeof(string))
variantType = VariantType.String;
else if (type == typeof(Type) || type == typeof(ScriptType))
variantType = VariantType.Typename;
else if (typeof(Asset).IsAssignableFrom(type))
variantType = VariantType.Asset;
else if (typeof(FlaxEngine.Object).IsAssignableFrom(type))
variantType = VariantType.Object;
else if (type == typeof(BoundingBox))
variantType = VariantType.BoundingBox;
else if (type == typeof(Transform))
variantType = VariantType.Transform;
else if (type == typeof(Ray))
variantType = VariantType.Ray;
else if (type == typeof(Matrix))
variantType = VariantType.Matrix;
else if (type == typeof(Vector2))
variantType = VariantType.Vector2;
else if (type == typeof(Vector3))
variantType = VariantType.Vector3;
else if (type == typeof(Vector4))
variantType = VariantType.Vector4;
else if (type == typeof(Color))
variantType = VariantType.Color;
else if (type == typeof(Guid))
variantType = VariantType.Guid;
else if (type == typeof(Quaternion))
variantType = VariantType.Quaternion;
else if (type == typeof(Rectangle))
variantType = VariantType.Rectangle;
else if (type == typeof(BoundingSphere))
variantType = VariantType.BoundingSphere;
else if (type.IsValueType)
variantType = VariantType.Structure;
else if (type == typeof(byte[]))
variantType = VariantType.Blob;
else if (type == typeof(object[]))
variantType = VariantType.Array;
else if (type == typeof(Dictionary