Multi-thread optimisation algorithm
This commit is contained in:
parent
5e0d2a514c
commit
03c429fa72
2 changed files with 217 additions and 113 deletions
|
@ -19,7 +19,7 @@ namespace Pixi.Common
|
||||||
{
|
{
|
||||||
public static class ColorSpaceUtility
|
public static class ColorSpaceUtility
|
||||||
{
|
{
|
||||||
private const float optimal_delta = 0.2f;
|
private const float optimal_delta = 0.1f;
|
||||||
|
|
||||||
private static Dictionary<BlockColor, float[]> colorMap = null;
|
private static Dictionary<BlockColor, float[]> colorMap = null;
|
||||||
|
|
||||||
|
@ -50,14 +50,14 @@ namespace Pixi.Common
|
||||||
if (geometricClosest < optimal_delta)
|
if (geometricClosest < optimal_delta)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{geometricClosest}");
|
//Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{geometricClosest}");
|
||||||
#endif
|
#endif
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{geometricClosest}");
|
//Logging.MetaLog($"Final delta ({closest[0]},{closest[1]},{closest[2]}) t:{geometricClosest}");
|
||||||
#endif
|
#endif
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Unity.Mathematics;
|
using Unity.Mathematics;
|
||||||
using Svelto.ECS;
|
using Svelto.ECS;
|
||||||
|
@ -10,6 +12,7 @@ using GamecraftModdingAPI;
|
||||||
using GamecraftModdingAPI.Blocks;
|
using GamecraftModdingAPI.Blocks;
|
||||||
using GamecraftModdingAPI.Commands;
|
using GamecraftModdingAPI.Commands;
|
||||||
using GamecraftModdingAPI.Utility;
|
using GamecraftModdingAPI.Utility;
|
||||||
|
using Svelto.DataStructures;
|
||||||
|
|
||||||
namespace Pixi.Common
|
namespace Pixi.Common
|
||||||
{
|
{
|
||||||
|
@ -41,12 +44,60 @@ namespace Pixi.Common
|
||||||
|
|
||||||
public Dictionary<int, Importer[]> importers = new Dictionary<int, Importer[]>();
|
public Dictionary<int, Importer[]> importers = new Dictionary<int, Importer[]>();
|
||||||
|
|
||||||
|
public static ThreadSafeDictionary<int, bool> optimisableBlockCache = new ThreadSafeDictionary<int, bool>();
|
||||||
|
|
||||||
public const float BLOCK_SIZE = 0.2f;
|
public const float BLOCK_SIZE = 0.2f;
|
||||||
|
|
||||||
public const float DELTA = BLOCK_SIZE / 2048;
|
public const float DELTA = BLOCK_SIZE / 2048;
|
||||||
|
|
||||||
public static int OPTIMISATION_PASSES = 2;
|
public static int OPTIMISATION_PASSES = 2;
|
||||||
|
|
||||||
|
public static int GROUP_SIZE = 64;
|
||||||
|
|
||||||
|
// optimisation algorithm constants
|
||||||
|
private static float3[] cornerMultiplicands1 = new float3[8]
|
||||||
|
{
|
||||||
|
new float3(1, 1, 1),
|
||||||
|
new float3(1, 1, -1),
|
||||||
|
new float3(-1, 1, 1),
|
||||||
|
new float3(-1, 1, -1),
|
||||||
|
new float3(-1, -1, 1),
|
||||||
|
new float3(-1, -1, -1),
|
||||||
|
new float3(1, -1, 1),
|
||||||
|
new float3(1, -1, -1),
|
||||||
|
};
|
||||||
|
private static float3[] cornerMultiplicands2 = new float3[8]
|
||||||
|
{
|
||||||
|
new float3(1, 1, 1),
|
||||||
|
new float3(1, 1, -1),
|
||||||
|
new float3(1, -1, 1),
|
||||||
|
new float3(1, -1, -1),
|
||||||
|
new float3(-1, 1, 1),
|
||||||
|
new float3(-1, 1, -1),
|
||||||
|
new float3(-1, -1, 1),
|
||||||
|
new float3(-1, -1, -1),
|
||||||
|
};
|
||||||
|
private static int[][] cornerFaceMappings = new int[][]
|
||||||
|
{
|
||||||
|
new int[] {0, 1, 2, 3}, // top
|
||||||
|
new int[] {2, 3, 4, 5}, // left
|
||||||
|
new int[] {4, 5, 6, 7}, // bottom
|
||||||
|
new int[] {6, 7, 0, 1}, // right
|
||||||
|
new int[] {0, 2, 4, 6}, // back
|
||||||
|
new int[] {1, 3, 5, 7}, // front
|
||||||
|
};
|
||||||
|
private static int[][] oppositeFaceMappings = new int[][]
|
||||||
|
{
|
||||||
|
new int[] {6, 7, 4, 5}, // bottom
|
||||||
|
new int[] {0, 1, 6, 7}, // right
|
||||||
|
new int[] {2, 3, 0, 1}, // top
|
||||||
|
new int[] {4, 5, 2, 3}, // left
|
||||||
|
new int[] {1, 3, 5, 7}, // front
|
||||||
|
new int[] {0, 2, 4, 6}, // back
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public CommandRoot()
|
public CommandRoot()
|
||||||
{
|
{
|
||||||
CommandManager.AddCommand(this);
|
CommandManager.AddCommand(this);
|
||||||
|
@ -144,7 +195,7 @@ namespace Pixi.Common
|
||||||
{
|
{
|
||||||
for (int pass = 0; pass < OPTIMISATION_PASSES; pass++)
|
for (int pass = 0; pass < OPTIMISATION_PASSES; pass++)
|
||||||
{
|
{
|
||||||
OptimiseBlocks(ref optVONs);
|
OptimiseBlocks(ref optVONs, (pass + 1) * GROUP_SIZE);
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Logging.MetaLog($"Optimisation pass {pass} completed");
|
Logging.MetaLog($"Optimisation pass {pass} completed");
|
||||||
#endif
|
#endif
|
||||||
|
@ -179,7 +230,66 @@ namespace Pixi.Common
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OptimiseBlocks(ref List<ProcessedVoxelObjectNotation> optVONs)
|
private void OptimiseBlocks(ref List<ProcessedVoxelObjectNotation> optVONs, int chunkSize)
|
||||||
|
{
|
||||||
|
// Reduce blocks to place to reduce lag while placing and from excessive blocks in the world.
|
||||||
|
// Blocks are reduced by grouping similar blocks that are touching (before they're placed)
|
||||||
|
// multithreaded because this is an expensive (slow) operation
|
||||||
|
int item = 0;
|
||||||
|
ProcessedVoxelObjectNotation[][] groups = new ProcessedVoxelObjectNotation[optVONs.Count / chunkSize][];
|
||||||
|
Thread[] tasks = new Thread[groups.Length];
|
||||||
|
while (item < groups.Length)
|
||||||
|
{
|
||||||
|
groups[item] = new ProcessedVoxelObjectNotation[chunkSize];
|
||||||
|
optVONs.CopyTo(item * chunkSize, groups[item], 0, chunkSize);
|
||||||
|
int tmpItem = item; // scope is dumb
|
||||||
|
tasks[item] = new Thread(() =>
|
||||||
|
{
|
||||||
|
groups[tmpItem] = groupBlocksBestEffort(groups[tmpItem], tmpItem);
|
||||||
|
});
|
||||||
|
tasks[item].Start();
|
||||||
|
item++;
|
||||||
|
}
|
||||||
|
#if DEBUG
|
||||||
|
Logging.MetaLog($"Created {groups.Length} + 1? groups");
|
||||||
|
#endif
|
||||||
|
// final group
|
||||||
|
ProcessedVoxelObjectNotation[] finalGroup = null;
|
||||||
|
Thread finalThread = null;
|
||||||
|
if (optVONs.Count > item * chunkSize)
|
||||||
|
{
|
||||||
|
//finalGroup = optVONs.GetRange(item * GROUP_SIZE, optVONs.Count - (item * GROUP_SIZE)).ToArray();
|
||||||
|
finalGroup = new ProcessedVoxelObjectNotation[optVONs.Count - (item * chunkSize)];
|
||||||
|
optVONs.CopyTo(item * chunkSize, finalGroup, 0, optVONs.Count - (item * chunkSize));
|
||||||
|
finalThread = new Thread(() =>
|
||||||
|
{
|
||||||
|
finalGroup = groupBlocksBestEffort(finalGroup, -1);
|
||||||
|
});
|
||||||
|
finalThread.Start();
|
||||||
|
}
|
||||||
|
// gather results
|
||||||
|
List<ProcessedVoxelObjectNotation> result = new List<ProcessedVoxelObjectNotation>();
|
||||||
|
for (int i = 0; i < groups.Length; i++)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Logging.MetaLog($"Waiting for completion of task {i}");
|
||||||
|
#endif
|
||||||
|
tasks[i].Join();
|
||||||
|
result.AddRange(groups[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalThread != null)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Logging.MetaLog($"Waiting for completion of final task");
|
||||||
|
#endif
|
||||||
|
finalThread.Join();
|
||||||
|
result.AddRange(finalGroup);
|
||||||
|
}
|
||||||
|
optVONs = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProcessedVoxelObjectNotation[] groupBlocksBestEffort(ProcessedVoxelObjectNotation[] blocksToOptimise, int id)
|
||||||
{
|
{
|
||||||
// a really complicated algorithm to determine if two similar blocks are touching (before they're placed)
|
// a really complicated algorithm to determine if two similar blocks are touching (before they're placed)
|
||||||
// the general concept:
|
// the general concept:
|
||||||
|
@ -194,17 +304,25 @@ namespace Pixi.Common
|
||||||
// this means it's not safe to assume that block A's common face (top) can be swapped with block B's non-common opposite face (top) to get the merged block
|
// this means it's not safe to assume that block A's common face (top) can be swapped with block B's non-common opposite face (top) to get the merged block
|
||||||
//
|
//
|
||||||
// note2: this does not work with blocks which aren't cubes (i.e. any block where rotation matters)
|
// note2: this does not work with blocks which aren't cubes (i.e. any block where rotation matters)
|
||||||
// TODO multithread this expensive operation
|
try
|
||||||
int item = 0;
|
|
||||||
while (item < optVONs.Count)
|
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Stopwatch timer = Stopwatch.StartNew();
|
||||||
|
#endif
|
||||||
|
FasterList<ProcessedVoxelObjectNotation> optVONs = new FasterList<ProcessedVoxelObjectNotation>(blocksToOptimise);
|
||||||
|
int item = 0;
|
||||||
|
while (item < optVONs.count - 1)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Logging.MetaLog($"({id}) Now grouping item {item}/{optVONs.count} ({100f * item/(float)optVONs.count}%)");
|
||||||
|
#endif
|
||||||
bool isItemUpdated = false;
|
bool isItemUpdated = false;
|
||||||
ProcessedVoxelObjectNotation itemVON = optVONs[item];
|
ProcessedVoxelObjectNotation itemVON = optVONs[item];
|
||||||
if (isOptimisableBlock(itemVON.block))
|
if (isOptimisableBlock(itemVON.block))
|
||||||
{
|
{
|
||||||
float3[] itemCorners = calculateCorners(itemVON);
|
float3[] itemCorners = calculateCorners(itemVON);
|
||||||
int seeker = item + 1; // despite this, assume that seeker goes thru the entire list (not just blocks after item)
|
int seeker = item + 1; // despite this, assume that seeker goes thru the entire list (not just blocks after item)
|
||||||
while (seeker < optVONs.Count)
|
while (seeker < optVONs.count)
|
||||||
{
|
{
|
||||||
if (seeker == item)
|
if (seeker == item)
|
||||||
{
|
{
|
||||||
|
@ -262,21 +380,23 @@ namespace Pixi.Common
|
||||||
item++;
|
item++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
|
timer.Stop();
|
||||||
|
Logging.MetaLog($"({id}) Completed best effort grouping of range in {timer.ElapsedMilliseconds}ms");
|
||||||
|
#endif
|
||||||
|
return optVONs.ToArray();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.MetaLog($"({id}) Exception occured...\n{e.ToString()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private float3[] calculateCorners(ProcessedVoxelObjectNotation von)
|
return blocksToOptimise;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static float3[] calculateCorners(ProcessedVoxelObjectNotation von)
|
||||||
{
|
{
|
||||||
float3[] cornerMultiplicands = new float3[8]
|
|
||||||
{
|
|
||||||
new float3(1, 1, 1),
|
|
||||||
new float3(1, 1, -1),
|
|
||||||
new float3(-1, 1, 1),
|
|
||||||
new float3(-1, 1, -1),
|
|
||||||
new float3(-1, -1, 1),
|
|
||||||
new float3(-1, -1, -1),
|
|
||||||
new float3(1, -1, 1),
|
|
||||||
new float3(1, -1, -1),
|
|
||||||
};
|
|
||||||
float3[] corners = new float3[8];
|
float3[] corners = new float3[8];
|
||||||
Quaternion rotation = Quaternion.Euler(von.rotation);
|
Quaternion rotation = Quaternion.Euler(von.rotation);
|
||||||
float3 rotatedScale = rotation * von.scale;
|
float3 rotatedScale = rotation * von.scale;
|
||||||
|
@ -284,24 +404,14 @@ namespace Pixi.Common
|
||||||
// generate corners
|
// generate corners
|
||||||
for (int i = 0; i < corners.Length; i++)
|
for (int i = 0; i < corners.Length; i++)
|
||||||
{
|
{
|
||||||
corners[i] = trueCenter + BLOCK_SIZE * (cornerMultiplicands[i] * rotatedScale / 2);
|
corners[i] = trueCenter + BLOCK_SIZE * (cornerMultiplicands1[i] * rotatedScale / 2);
|
||||||
}
|
}
|
||||||
return corners;
|
return corners;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateVonFromCorners(float3[] corners, ref ProcessedVoxelObjectNotation von)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void updateVonFromCorners(float3[] corners, ref ProcessedVoxelObjectNotation von)
|
||||||
{
|
{
|
||||||
float3[] cornerMultiplicands = new float3[8]
|
|
||||||
{
|
|
||||||
new float3(1, 1, 1),
|
|
||||||
new float3(1, 1, -1),
|
|
||||||
new float3(1, -1, 1),
|
|
||||||
new float3(1, -1, -1),
|
|
||||||
new float3(-1, 1, 1),
|
|
||||||
new float3(-1, 1, -1),
|
|
||||||
new float3(-1, -1, 1),
|
|
||||||
new float3(-1, -1, -1),
|
|
||||||
};
|
|
||||||
float3 newCenter = sumOfFloat3Arr(corners) / corners.Length;
|
float3 newCenter = sumOfFloat3Arr(corners) / corners.Length;
|
||||||
float3 newPosition = newCenter;
|
float3 newPosition = newCenter;
|
||||||
Quaternion rot = Quaternion.Euler(von.rotation);
|
Quaternion rot = Quaternion.Euler(von.rotation);
|
||||||
|
@ -311,26 +421,9 @@ namespace Pixi.Common
|
||||||
//Logging.MetaLog($"Updated VON scale {von.scale} (absolute {rotatedScale})");
|
//Logging.MetaLog($"Updated VON scale {von.scale} (absolute {rotatedScale})");
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[][] findMatchingCorners(float3[] corners1, float3[] corners2)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int[][] findMatchingCorners(float3[] corners1, float3[] corners2)
|
||||||
{
|
{
|
||||||
int[][] cornerFaceMappings = new int[][]
|
|
||||||
{
|
|
||||||
new int[] {0, 1, 2, 3}, // top
|
|
||||||
new int[] {2, 3, 4, 5}, // left
|
|
||||||
new int[] {4, 5, 6, 7}, // bottom
|
|
||||||
new int[] {6, 7, 0, 1}, // right
|
|
||||||
new int[] {0, 2, 4, 6}, // back
|
|
||||||
new int[] {1, 3, 5, 7}, // front
|
|
||||||
};
|
|
||||||
int[][] oppositeFaceMappings = new int[][]
|
|
||||||
{
|
|
||||||
new int[] {6, 7, 4, 5}, // bottom
|
|
||||||
new int[] {0, 1, 6, 7}, // right
|
|
||||||
new int[] {2, 3, 0, 1}, // top
|
|
||||||
new int[] {4, 5, 2, 3}, // left
|
|
||||||
new int[] {1, 3, 5, 7}, // front
|
|
||||||
new int[] {0, 2, 4, 6}, // back
|
|
||||||
};
|
|
||||||
float3[][] faces1 = facesFromCorners(corners1);
|
float3[][] faces1 = facesFromCorners(corners1);
|
||||||
float3[][] faces2 = facesFromCorners(corners2);
|
float3[][] faces2 = facesFromCorners(corners2);
|
||||||
for (byte i = 0; i < faces1.Length; i++)
|
for (byte i = 0; i < faces1.Length; i++)
|
||||||
|
@ -355,7 +448,8 @@ namespace Pixi.Common
|
||||||
}
|
}
|
||||||
|
|
||||||
// this assumes the corners are in the order that calculateCorners outputs
|
// this assumes the corners are in the order that calculateCorners outputs
|
||||||
private float3[][] facesFromCorners(float3[] corners)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static float3[][] facesFromCorners(float3[] corners)
|
||||||
{
|
{
|
||||||
return new float3[][]
|
return new float3[][]
|
||||||
{
|
{
|
||||||
|
@ -368,7 +462,8 @@ namespace Pixi.Common
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] matchFace(float3[] face1, float3[] face2)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int[] matchFace(float3[] face1, float3[] face2)
|
||||||
{
|
{
|
||||||
int[] result = new int[4];
|
int[] result = new int[4];
|
||||||
byte count = 0;
|
byte count = 0;
|
||||||
|
@ -396,7 +491,8 @@ namespace Pixi.Common
|
||||||
return new int[0];
|
return new int[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private float3 sumOfFloat3Arr(float3[] arr)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static float3 sumOfFloat3Arr(float3[] arr)
|
||||||
{
|
{
|
||||||
float3 total = float3.zero;
|
float3 total = float3.zero;
|
||||||
for (int i = 0; i < arr.Length; i++)
|
for (int i = 0; i < arr.Length; i++)
|
||||||
|
@ -408,12 +504,19 @@ namespace Pixi.Common
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool isOptimisableBlock(BlockIDs block)
|
private static bool isOptimisableBlock(BlockIDs block)
|
||||||
{
|
{
|
||||||
return block.ToString().EndsWith("Cube", StringComparison.InvariantCultureIgnoreCase);
|
if (optimisableBlockCache.ContainsKey((int) block))
|
||||||
|
{
|
||||||
|
return optimisableBlockCache[(int) block];
|
||||||
}
|
}
|
||||||
|
|
||||||
private string float3ArrToString(float3[] arr)
|
bool result = block.ToString().EndsWith("Cube", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
optimisableBlockCache[(int) block] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string float3ArrToString(float3[] arr)
|
||||||
{
|
{
|
||||||
string result = "[";
|
string result = "[";
|
||||||
foreach (float3 f in arr)
|
foreach (float3 f in arr)
|
||||||
|
@ -434,10 +537,11 @@ namespace Pixi.Common
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Logging.CommandLogError("RIP\n" + e);
|
Logging.CommandLogError("RIP Pixi\n" + e);
|
||||||
#else
|
#else
|
||||||
Logging.CommandLogError("Pixi failed (reason: " + e.Message + ")");
|
Logging.CommandLogError("Pixi failed (reason: " + e.Message + ")");
|
||||||
#endif
|
#endif
|
||||||
|
Logging.LogWarning("Pixi Error\n" + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue