I'm currently learning how to code games and have designed a biom generation algorithm.
As long as I run that algorithm below syncron, it generates the same output every time and works perfectly fine.
Now I tried to speed it up and make it multithreaded, but every time I call the method, it results in a different result.
As far as I know, I used Threadsave Collections, whenever necessary, but it still doesn't work.
Also, I tried to lock the collection, but this didn't work either.
So I'm completely clueless as to why this doesn't work.
If you see anything that I could make better or how I could fix that problem, please let me know.
This code is working:
private Biome[,] Generate(string worldSeed, Vector2Int targetChunk, List<(Biome, float)> multiplier, float centroidsPerChunk)
{
//Calculate the NeighboursToGenerate depeding on the cendroids per Chunk value
int chunkNeighboursToGenerate = (int)Math.Ceiling(Math.Sqrt(1f / centroidsPerChunk * 12.5f));
int chunkSize = 8;
//Create List that contains all centroids of the chunk
List<(Vector2Int, Biome)> centroids = new();
//Create Centdroids for every chunk of the generated region around the targetchunk
for (int chunkX = targetChunk.x - chunkNeighboursToGenerate; chunkX < targetChunk.x + chunkNeighboursToGenerate + 1; chunkX++)
{
for (int chunkZ = targetChunk.y - chunkNeighboursToGenerate; chunkZ < targetChunk.y + chunkNeighboursToGenerate + 1; chunkZ++)
{
List<(Vector2Int, Biome)> generatedCentdroids = GetCentdroidsByChunk(worldSeed, new(chunkX, chunkZ), centroidsPerChunk, chunkSize, multiplier, targetChunk, chunkNeighboursToGenerate);
foreach ((Vector2Int, Biome) generatedCentdroid in generatedCentdroids)
{
centroids.Add(generatedCentdroid);
}
}
}
Biome[,] biomeMap = new Biome[chunkSize, chunkSize];
//---Generate biomeMap of the target Chunk---
for (int tx = 0; tx < chunkSize; tx++)
{
for (int tz = 0; tz < chunkSize; tz++)
{
int x = chunkSize * chunkNeighboursToGenerate + tx;
int z = chunkSize * chunkNeighboursToGenerate + tz;
biomeMap[tz, tx] = GetClosestCentroidBiome(new(x, z), centroids.ToArray());
};
};
//Return the biome map of the target chunk
return biomeMap;
}
private static List<(Vector2Int, Biome)> GetCentdroidsByChunk(string worldSeed, Vector2Int chunkToGenerate, float centroidsPerChunk, int chunkSize, List<(Biome, float)> multiplier, Vector2Int targetChunk, int chunkNeighboursToGenerate)
{
List<(Vector2Int, Biome)> centroids = new();
//---Generate Cendroids of a single chunk---
float centroidsInThisChunk = centroidsPerChunk;
//Init randomizer
System.Random randomInstance = new(Randomizer.GetSeed(worldSeed, chunkToGenerate.x, chunkToGenerate.y));
while (centroidsInThisChunk > 0.0f)
{
//if at least one more centroid is to generate do it
//if not randomize by the given probability if another one should be generated
if (centroidsInThisChunk >= 1 || (float)randomInstance.NextDouble() * (1 - 0) + 0 <= centroidsInThisChunk)
{
//Generate random point for a new centroid
Vector2Int pos = new(randomInstance.Next(0, chunkSize + 1), randomInstance.Next(0, chunkSize + 1));
//map the point to a zerobased coordinatesystem
int mappedX = (((chunkToGenerate.x - targetChunk.x) + chunkNeighboursToGenerate) * chunkSize) + pos.x;
int mappedZ = (((chunkToGenerate.y - targetChunk.y) + chunkNeighboursToGenerate) * chunkSize) + pos.y;
Vector2Int mappedPos = new Vector2Int(mappedX, mappedZ);
//Select the biom randomized
Biome biome = Randomizer.GetRandomBiom(randomInstance, multiplier);
centroids.Add(new(mappedPos, biome));
centroidsInThisChunk -= 1.0f;
}
//if no centroid is left to generate, end the loop
else
{
break;
}
}
return centroids;
}
//Calculates the closest Centroid to the given possition
Biome GetClosestCentroidBiome(Vector2Int pixelPos, IEnumerable<(Vector2Int, Biome)> centroids)
{
//Warp the possition so the biom borders won't be straight
//Vector2 warpedPos = pixelPos + Get2DTurbulence(pixelPos);
Vector2 warpedPos = pixelPos;
float smallestDst = float.MaxValue;
Biome closestBiome = Biome.Empty;
foreach ((Vector2Int, Biome) centroid in centroids)
{
float distance = Vector2.Distance(warpedPos, centroid.Item1);
if (distance < smallestDst)
{
smallestDst = distance;
closestBiome = centroid.Item2;
}
}
return closestBiome;
}
public static class Randomizer
{
//Generates a random integerseed by combining an hashing the inputvalues
public static int GetSeed(string worldSeed, int chunkx, int chunkz)
{
var stringSeed = worldSeed + ":" + chunkx + ";" + chunkz;
MD5 md5Hasher = MD5.Create();
byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(stringSeed));
return BitConverter.ToInt32(hashed, 0);
}
//Returns a random biome based on the given properbilities/multiplier
//multiplier = 2 for example means the biom is generated twice as often as usually
public static Biome GetRandomBiom(System.Random rndm, List<(Biome, float)> multiplier)
{
float multmax = 0.0f;
multiplier.ForEach(x => multmax += x.Item2);
//Generate a random value that is in the range of all multiplieres added
float biome = (float)rndm.NextDouble() * (multmax + 0.01f);
//Map the biome to the multipliers and return the biome
float multcalc = 0.0f;
for (int r = 0; r < multiplier.Count; r++)
{
multcalc += multiplier[r].Item2;
if (multcalc >= biome)
{
return multiplier[r].Item1;
}
}
//Return Biome.Empty if something did't worked correct
return Biome.Empty;
}
}
This doesn't work:
private Biome[,] Generate(string worldSeed, Vector2Int targetChunk, List<(Biome, float)> multiplier, float centroidsPerChunk)
{
//Calculate the NeighboursToGenerate depeding on the cendroids per Chunk value
int chunkNeighboursToGenerate = (int)Math.Ceiling(Math.Sqrt(1f / centroidsPerChunk * 12.5f));
int chunkSize = 8;
//Create List that contains all centroids of the chunk
ConcurrentBag<(Vector2Int, Biome)> centroids = new();
ConcurrentQueue<Task> tasks = new();
//Create Centdroids for every chunk of the generated region around the targetchunk
for (int chunkX = targetChunk.x - chunkNeighboursToGenerate; chunkX < targetChunk.x + chunkNeighboursToGenerate + 1; chunkX++)
{
for (int chunkZ = targetChunk.y - chunkNeighboursToGenerate; chunkZ < targetChunk.y + chunkNeighboursToGenerate + 1; chunkZ++)
{
tasks.Enqueue(Task.Run(() =>
{
List<(Vector2Int, Biome)> generatedCentdroids = GetCentdroidsByChunk(worldSeed, new(chunkX, chunkZ), centroidsPerChunk, chunkSize, multiplier, targetChunk, chunkNeighboursToGenerate);
foreach ((Vector2Int, Biome) generatedCentdroid in generatedCentdroids)
{
centroids.Add(generatedCentdroid);
}
}));
}
}
Biome[,] biomeMap = new Biome[chunkSize, chunkSize];
Task.WaitAll(tasks.ToArray());
//---Generate biomeMap of the target Chunk---
for (int tx = 0; tx < chunkSize; tx++)
{
for (int tz = 0; tz < chunkSize; tz++)
{
int x = chunkSize * chunkNeighboursToGenerate + tx;
int z = chunkSize * chunkNeighboursToGenerate + tz;
biomeMap[tz, tx] = GetClosestCentroidBiome(new(x, z), centroids.ToArray());
};
};
//Return the biome map of the target chunk
return biomeMap;
}
If you're starting to get into programming and you want to learn multi-threading, converting a large piece of complex code like this is not where you want to start. I highly recommend you pick up a book or tutorial on threading/async in C#/.NET before starting something like this. Unity also has its own multi-threading library with its Job System, which is built for the Unity workflow: https://docs.unity3d.com/Manual/JobSystemMultithreading.html
I don't think most people could find what's causing the problem in these two code snippets alone. But I have a couple of suggestions
Change your tasks collection to a List<T>, tasks is only ever accessed on one thread so there's no need to use ConcurrentQueue<T>
Is Biome a class? Cause if so it's technically fine but modifying data structures from multiple threads gets hairy fast. And while I can't see that you're modifying data from these snippets, without the full code I can't say for sure. Turn Biome into a struct or make a struct equivalent for threading purposes.
Also avoid calling centroids.ToArray() in your loop, as doing so will actually copy the original array over and over and over again. Call it once outside of your loop and that alone should be a pretty huge performance bump.
Just find a full-blown tutorial for threading/async/Unity's Job system (depending on which you'd rather learn for your use case) and start from there, I can tell from your use of the concurrent libraries and List<T> inside your tasks that you're new to threading. Understanding what code is ran on another thread and the repercussions from that (race conditions, and so on) is huge.
Does anyone know a more efficient way of doing this code:
public static float ConvertTrafficValues(double bytes, out string speedBytes)
{
if (bytes >= 1000000)
{
bytes /= 1000000;
bytes = Math.Round(bytes, 1);
speedBytes = "MB/s";
}
else if (bytes >= 1000)
{
bytes /= 1000;
bytes = Math.Round(bytes, 0);
speedBytes = "KB/s";
}
else
{
bytes = Math.Round(bytes, 0);
speedBytes = "B/s";
}
return (float)bytes;
}
Im calling this multiple times every second alongside with some other things and I need it to be as efficient as possible
I coded the following solution based on the accepted answer of this question:
private static readonly string[] s_Suffixes = { "B/s", "KB/s", "MB/s" };
public static Single ConvertTrafficValues(Double bytes, out String speedBytes)
{
if (bytes == 0.0d)
{
speedBytes = "B/s";
return 0.0f;
}
Int32 magnitude = (Int32)Math.Log(bytes, 1024.0d);
Double size;
if (magnitude >= (s_Suffixes.Length - 1))
{
magnitude = s_Suffixes.Length - 1;
size = bytes / Math.Pow(2.0d, magnitude * 10);
}
else
{
size = bytes / Math.Pow(2.0d, magnitude * 10);
if (Math.Round(size, 2) >= 1000.0d)
{
magnitude += 1;
size /= 1024.0d;
}
}
speedBytes = s_Suffixes[magnitude];
return (Single)size;
}
Even if it concerns file volumes, the underlying logic is almost the same. I limited the maximum magnitude to Mb since this is what your method is doing. I didn't change your implementation logics since you should know how and when to use the method much better than me.
I am trying too have my C# console application make a beep sound. Yes I know I can use Console.Beep but I also want to lower the volume etc.
But the error I am getting is this:
Method name expected
on this line:
binaryWriter.Write(hdr(i));
This is my code:
private bool Beep(int volume, int frequency, int duration)
{
try
{
double amplitude = volume * 1.27;
double a = ((amplitude * (System.Math.Pow(2, 15))) / 1000) - 1;
double deltaFt = 2 * System.Math.PI * frequency / 8000;
double samples = 441 * (duration / 100);
int bytes = Convert.ToInt32(samples) * 4;
int[] hdr = {
0x46464952,
36 + bytes,
0x45564157,
0x20746d66,
16,
0x20001,
8000,
176400,
0x100004,
0x61746164,
bytes
};
using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(44 + bytes))
{
using (System.IO.BinaryWriter binaryWriter = new System.IO.BinaryWriter(memoryStream))
{
for (int i = 0; i <= hdr.Length - 1; i++)
{
binaryWriter.Write(hdr(i));
}
for (int T = 0; T <= Convert.ToInt32(samples) - 1; T++)
{
short sample = Convert.ToInt16(a * System.Math.Sin(deltaFt * T));
binaryWriter.Write(sample);
binaryWriter.Write(sample);
}
binaryWriter.Flush();
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer(memoryStream))
{
sp.PlaySync();
}
}
}
}
catch
{
return false;
}
return true;
}
Your hdr is an array, you need to get the entry by putting the square brackets and then passing the index
binaryWriter.Write(hdr[i]);
What I am making is a small audio editor that loads a .wav file and displays it in the time domain. The user can select a part of it and zoom in or DFT the chunk which displays a small window in the frequency domain. (extra functionality to be added later)
I think I am making a mistake when splitting my byte array into two float arrays.
I use NAudio to get my samples into a byte array.
Then I use a loop I found on stack overflow to split the array into left and right channels.
private void readToArrays(WaveFileReader pcm) {
wf = pcm.WaveFormat;
int samplesDesired = (int)pcm.Length;
buffer = new byte[samplesDesired * 4];
left = new float[samplesDesired];
right = new float[samplesDesired];
int bytesRead = pcm.Read(buffer, 0, samplesDesired);
if (wf.BitsPerSample == 16) {
if (wf.Channels == 1) {
for (int i = 0; i < buffer.Length / 4; i++)
//handle
}
else if (wf.Channels == 2) {
int index = 0;
for (int sample = 0; sample < bytesRead / 4; sample++) {
left[sample] = BitConverter.ToInt16(buffer, index);
index += 2;
right[sample] = BitConverter.ToInt16(buffer, index);
index += 2;
}
}
}else if (wf.BitsPerSample == 8) {
if (wf.Channels == 1) {
//handle
}
else if (wf.Channels == 2) {
//handle
}
}
}
TL:DR: I get a lot of noise from playback with individual files. What I need is some advice as to how I can still be able to modify my samples / DFT them AND be able to output them without noise. Do note that I am just a 2nd year Computing student and do not have years of experience.
Extra Info : Bit Depth = 16; Sample Rate = 22050;
I have this function:
int round(double val) {
if (val >= 0) {
return (int)Math.Floor(val + 0.5);
}
return (int)Math.Ceiling(val - 0.5);
}
I call it in my program many times, I mean MANY times, so every milisecond of it's running time matters. Is there any way of having it faster than it is now? Thx
EDIT:
The function is part of algorithm for computing the tangent direction of a line in an image. It is taken from academic article. As it handles the angles in radian values, it uses small, precise numbers.
I/O examples:
0 -> 0
1 -> 1
1.1 -> 1
1.51 -> 2
-0.1 -> 0
-1 -> -1
-1.1 -> -1
-1.51 -> -2
EDIT2:
According to comments, I change the examined function to this one:
int round(double val) {
return (int)Math.Round(val, MidpointRounding.AwayFromZero);
}
The updated question is: Is the Math.Round function the fastest way of rounding?
You can speed it up. This is many times faster:
if (val >= 0)
{
return (int)(val + 0.5d);
}
return = (int)(val - 0.5d);
You avoid all those Math library stuff. Problem is, does it really matter? For 1500000 conversions the time of You first function is 18ms. Your EDIT2 function is 36ms. This function is 4ms.
According to this measurement the processor can compare two doubles, add two doubles and convert one in about 2,5ns. But if it does not have it in the cache, reading from main memory can take 100ns. Measuring can be misleading sometimes.
Here is complete code
#region stopky
public class Stopky
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool QueryPerformanceFrequency(out long frequency);
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool QueryPerformanceCounter(out long ticks);
protected static double frequency = -1;
public void setStart()
{
QueryPerformanceCounter(out tickStart);
}
public double getTimeFromStart
{
get
{
QueryPerformanceCounter(out tickNow);
double time = (tickNow - tickStart) / frequency;
return time;
}
}
private long tickStart;
private long tickNow;
public Stopky()
{
if (frequency < 0)
{
long tmp;
QueryPerformanceFrequency(out tmp);
if (tmp == 0)
{
throw new NotSupportedException("Error while querying "
+ "the high-resolution performance counter.");
}
frequency = tmp;
}
setStart();
}
public void Show()
{
MessageBox.Show(this.getTimeFromStart.ToString());
}
}
#endregion
private void button2_Click(object sender, EventArgs e)
{
double[] examples = new double[] { 0, 1, 1.1, 1.51, -0.1, -1, -1.1, -1.51 };
int totalCount = 1500000;
double[] examplesExpanded = new double[totalCount];
for (int i = 0, j = 0; i < examplesExpanded.Length; ++i)
{
examplesExpanded[i] = examples[j];
if (++j >= examples.Length) { j = 0; }
}
int[] result1 = new int[totalCount];
int[] result2 = new int[totalCount];
int[] result3 = new int[totalCount];
Stopky st = new Stopky();
for (int i = 0; i < examplesExpanded.Length; ++i)
{
result1[i] = (int)Math.Round(examplesExpanded[i], MidpointRounding.AwayFromZero);
}
st.Show();
st = new Stopky();
for (int i = 0; i < examplesExpanded.Length; ++i)
{
double val = examplesExpanded[i];
if (val >= 0)
{
result2[i] = (int)Math.Floor(val + 0.5);
}
result2[i] = (int)Math.Ceiling(val - 0.5);
}
st.Show();
st = new Stopky();
for (int i = 0; i < examplesExpanded.Length; ++i)
{
double val = examplesExpanded[i];
if (val >= 0)
{
result3[i] = (int)(val + 0.5d);
}
else
{
result3[i] = (int)(val - 0.5d);
}
}
st.Show();
for (int i = 0; i < totalCount; ++i)
{
if(result1[i] != result2[i] || result1[i] != result3[i])
{
MessageBox.Show("ERROR");
}
}
MessageBox.Show("OK");
}
Some notes
i < examplesExpanded.Length is slightly faster than i < totalCount, although it is counter intuitive. The reason is, range check can be avoided.
Release can be significantly faster than Debug (here is the difference not big)
Why not use the inbuilt Math.Round method?
int round(double val) {
if (val >= 0) {
return Math.Round(val, MidpointRounding.AwayFromZero);
}
return Math.Round(val, MidpointRounding.ToEven);
}
https://msdn.microsoft.com/en-us/library/system.math.round(v=vs.110).aspx