16 Commits

Author SHA1 Message Date
28ada065a6 fixed stuck pixels 2025-05-01 22:50:35 +02:00
ea34b8ecc0 moved tick to process from physics process
fixed fall speed
improved naming
2025-05-01 22:46:51 +02:00
4462573267 fixed element leak across chunks 2025-04-30 23:23:20 +02:00
0b97027e00 fixed stuck rain
refactoring
2025-04-30 23:13:53 +02:00
d9bde10c3c fixed rain 2025-04-27 15:31:20 +02:00
2e4fc22263 enabled overwrite of existing pixels 2025-04-27 15:21:56 +02:00
202e71d97e improved debug color 2025-04-27 15:19:49 +02:00
a119e92bf6 fixed brush drawing across chunk borders 2025-04-27 15:16:10 +02:00
da3afd22fc fixed drawpixel optimization
minor class refactoring
2025-04-27 15:05:40 +02:00
abf948a310 adjusted debug mode 2025-04-27 14:15:14 +02:00
2cd6a757dc fixed memory leak
fixed trying to draw out of bounds
2025-04-27 14:00:54 +02:00
4ce265e722 fixed unnecessary write to texture if no changes happened 2025-04-27 13:49:35 +02:00
572e02956c fixed benchmark placement 2025-04-27 13:18:18 +02:00
de99d54ad6 fixed brush Y dimension 2025-04-27 13:04:06 +02:00
2bcdeecb81 Merge branch 'performance-improvements' 2025-04-21 18:57:20 +02:00
7b2845ee01 added benchmark function
added .txt for benchmark results
fixed generating level message
2024-10-27 14:08:19 +01:00
9 changed files with 131 additions and 69 deletions

View File

@@ -3,13 +3,10 @@ using Godot;
public partial class PerfDetails : Label public partial class PerfDetails : Label
{ {
[Export]
public bool ShowDetails = true;
public override void _Process(double delta) public override void _Process(double delta)
{ {
Text = ""; Text = "";
if (!ShowDetails) return; if (!Main.Instance.DebugMode) return;
int activeElements = 0; int activeElements = 0;
foreach (Chunk c in Main.Instance.Level.GetChunks()) foreach (Chunk c in Main.Instance.Level.GetChunks())

View File

@@ -1,4 +1,5 @@
using System.Reflection; using System.Reflection;
using FOU.Scripts;
using Godot; using Godot;
public partial class SettingsController : VBoxContainer public partial class SettingsController : VBoxContainer
@@ -18,7 +19,7 @@ public partial class SettingsController : VBoxContainer
slSize.Value = main.BrushSize; slSize.Value = main.BrushSize;
slRain.ValueChanged += OnRainValueChanged; slRain.ValueChanged += OnRainValueChanged;
slRain.Value = main.RainAmount; slRain.Value = main.rainAmount;
} }
private void OnSizeValueChanged(double value) { private void OnSizeValueChanged(double value) {

View File

@@ -7,9 +7,8 @@
[node name="Main" type="Node2D"] [node name="Main" type="Node2D"]
script = ExtResource("1_k1i8e") script = ExtResource("1_k1i8e")
DebugVisualization = true DebugMode = true
BrushSize = 2 BrushSize = 4
RainAmount = 1.0
[node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="CanvasLayer" type="CanvasLayer" parent="."]
@@ -85,6 +84,6 @@ custom_minimum_size = Vector2(150, 0)
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 8 size_flags_horizontal = 8
size_flags_vertical = 1 size_flags_vertical = 1
max_value = 5.0 max_value = 10.0
step = 0.01 step = 0.01
value = 1.0 value = 1.0

View File

@@ -54,7 +54,7 @@ public class Chunk {
if (toAddElements.Count > 0) { if (toAddElements.Count > 0) {
foreach (Element addElement in toAddElements) { foreach (Element addElement in toAddElements) {
activeElements.Add(addElement); activeElements.Add(addElement);
addElement.SetDebugColor(Colors.Green); addElement.SetDebugColor(addElement.Color * 1.5f);
} }
toAddElements.Clear(); toAddElements.Clear();
} }
@@ -76,8 +76,8 @@ public class Chunk {
Type type = typeof(T); Type type = typeof(T);
object o = Activator.CreateInstance(type, x, y, this); object o = Activator.CreateInstance(type, x, y, this);
// check if not empty: // check if not empty and overwrite not allowed:
if (Elements[x,y].GetType() != typeof(Element)) { if (!Elements[x,y].IsEmpty() && !Main.Instance.AllowOverwrite) {
return; return;
} }
@@ -138,6 +138,9 @@ public class Chunk {
if (what.Chunk != swapTo.Chunk) { if (what.Chunk != swapTo.Chunk) {
what.Chunk.RemoveFromChunk(what); what.Chunk.RemoveFromChunk(what);
swapTo.Chunk.AddToChunk(what); swapTo.Chunk.AddToChunk(what);
swapTo.Chunk.RemoveFromChunk(swapTo);
what.Chunk.AddToChunk(swapTo);
} }
Element temp = new Element(what); Element temp = new Element(what);

View File

@@ -2,7 +2,7 @@
namespace FOU.Scripts.Elements; namespace FOU.Scripts.Elements;
public class Element { public class Element{
public Chunk Chunk; public Chunk Chunk;
public Vector2I Position; public Vector2I Position;
@@ -15,11 +15,12 @@ public class Element {
protected const float MAX_COLOR_VARIANCE = 0.1f; protected const float MAX_COLOR_VARIANCE = 0.1f;
protected static readonly Vector2I VERTICAL_OPPOSITE = new Vector2I(-1, 1); protected static readonly Vector2I VERTICAL_OPPOSITE = new Vector2I(-1, 1);
protected int lastMove = 0;
private bool active = false; private bool active = false;
private int lastUpdate = -1; private int lastUpdate = -1;
private int lastMove = 0;
private Color originalColor; private Color originalColor;
private bool markedForUpdate = false;
public Element(Element e) { public Element(Element e) {
Position = e.Position; Position = e.Position;
@@ -31,8 +32,7 @@ public class Element {
Position.X = x; Position.X = x;
Position.Y = y; Position.Y = y;
Chunk = chunk; Chunk = chunk;
lastMove = Engine.GetFramesDrawn(); Active = true;
Active = false;
} }
public bool Active { public bool Active {
@@ -42,7 +42,12 @@ public class Element {
active = value; active = value;
Chunk.SetElementActive(this, value); Chunk.SetElementActive(this, value);
// SetDebugColor(value, new Color(0.2f, 0.2f, 0.2f)); lastMove = Engine.GetFramesDrawn();
if (!active)
ResetColor();
else
Moved();
} }
} }
@@ -56,8 +61,13 @@ public class Element {
public virtual bool Update() { public virtual bool Update() {
if (!Active) return false; if (!Active) return false;
if (lastUpdate == Engine.GetFramesDrawn()) return false; // already updated this frame int frame = Engine.GetFramesDrawn();
lastUpdate = Engine.GetFramesDrawn(); if (lastMove + STEPS_UNTIL_INACTIVE < frame)
Active = false;
if (lastUpdate == frame)
return false; // already updated this frame
lastUpdate = frame;
return true; return true;
} }
@@ -78,10 +88,11 @@ public class Element {
else if (Chunk.IsEmpty(Position + randomDirection * VERTICAL_OPPOSITE)) else if (Chunk.IsEmpty(Position + randomDirection * VERTICAL_OPPOSITE))
Chunk.Swap(this, Position + randomDirection * VERTICAL_OPPOSITE); Chunk.Swap(this, Position + randomDirection * VERTICAL_OPPOSITE);
if (GD.Randi() % MAX_DIFFUSE_SPEED > DiffuseSpeed) return; // ascend slower // density check
if (Chunk.Get(Position + Vector2I.Down)?.Density < Density)
if (Chunk.Get(Position + randomDirection)?.Density < Density)
Chunk.Swap(this, Position + Vector2I.Down); Chunk.Swap(this, Position + Vector2I.Down);
else if (Chunk.Get(Position + Vector2I.Down + randomDirection)?.Density < Density)
Chunk.Swap(this, Position + Vector2I.Down + randomDirection);
} }
protected Vector2I RandomDirectionDown() { protected Vector2I RandomDirectionDown() {
@@ -91,6 +102,13 @@ public class Element {
+ (GD.Randi() % 2 != 0 ? Vector2I.Zero : Vector2I.Right * randomDirection); + (GD.Randi() % 2 != 0 ? Vector2I.Zero : Vector2I.Right * randomDirection);
} }
/// <returns>-1, 0 or 1</returns>
protected Vector2I RandomDirection() {
int randomDirection = GD.Randi() % 2 != 0 ? 1 : -1;
return (GD.Randi() % 2 != 0 ? Vector2I.Zero : Vector2I.Right * randomDirection);
}
protected Color AddColorVariance(Color baseColor) { protected Color AddColorVariance(Color baseColor) {
Color c = baseColor; Color c = baseColor;
c.R += (GD.Randf() - 1) * MAX_COLOR_VARIANCE; c.R += (GD.Randf() - 1) * MAX_COLOR_VARIANCE;
@@ -103,15 +121,29 @@ public class Element {
public void ResetColor() { public void ResetColor() {
color = originalColor; color = originalColor;
MarkForUpdate();
} }
public void SetDebugColor(Color color) { public void SetDebugColor(Color color) {
if (!Main.Instance.DebugVisualization) return; if (!Main.Instance.DebugMode) return;
this.color = color; this.color = color;
} }
public void Moved() { public void Moved() {
lastMove = Engine.GetFramesDrawn(); lastMove = Engine.GetFramesDrawn();
MarkForUpdate();
}
public bool IsMarkedForUpdate() {
return markedForUpdate;
}
public void MarkForUpdate(bool mark = true) {
markedForUpdate = true;
}
public bool IsEmpty() {
return GetType() == typeof(Element);
} }
} }

View File

@@ -5,11 +5,12 @@ namespace FOU.Scripts.Elements;
public abstract class Liquid : Element { public abstract class Liquid : Element {
public const int MAX_VERTICAL_SPREAD = 3; public const int MAX_VERTICAL_SPREAD = 3;
protected Liquid(int x, int y, ref Chunk chunk) : base(x, y, chunk) { } protected Liquid(int x, int y, ref Chunk chunk) : base(x, y, chunk) {
MarkForUpdate();
}
public override bool Update() { public override bool Update() {
if (!base.Update()) return false; if (!base.Update()) return false;
if (lastMove + STEPS_UNTIL_INACTIVE < Engine.GetFramesDrawn()) Active = false;
Tick(); Tick();

View File

@@ -3,11 +3,12 @@
namespace FOU.Scripts.Elements; namespace FOU.Scripts.Elements;
public abstract class Solid : Element { public abstract class Solid : Element {
protected Solid(int x, int y, ref Chunk chunk) : base(x, y, chunk) { } protected Solid(int x, int y, ref Chunk chunk) : base(x, y, chunk) {
MarkForUpdate();
}
public override bool Update() { public override bool Update() {
if (!base.Update()) return false; if (!base.Update()) return false;
if (lastMove + STEPS_UNTIL_INACTIVE < Engine.GetFramesDrawn()) Active = false;
Tick(); Tick();

View File

@@ -30,7 +30,7 @@ public class Level {
GD.Print($"Generating level ({sizeX}:{sizeY}) with {chunksPerX*chunksPerY} chunks ({chunkResX} * {chunkResY})"); GD.Print($"Generating level ({sizeX}:{sizeY}) with {chunksPerX*chunksPerY} chunks ({chunkResX} * {chunkResY})");
image = Image.Create(sizeX, sizeY, false, Image.Format.Rgb8); image = Image.CreateEmpty(sizeX, sizeY, false, Image.Format.Rgb8);
chunks = new Chunk[chunksPerX, chunksPerY]; chunks = new Chunk[chunksPerX, chunksPerY];
int index = 0; int index = 0;
@@ -69,7 +69,7 @@ public class Level {
GD.Print("benchmark"); GD.Print("benchmark");
for (int x = 0; x < chunksPerX; x++) { for (int x = 0; x < chunksPerX; x++) {
for (int y = 0; y < chunksPerY; y++) { for (int y = 0; y < chunksPerY; y++) {
WritePixel<Dirt>(x * chunkResX/2, y * chunkResY/2, 100); WritePixel<Dirt>((x * chunkResX) + chunkResX/2, (y * chunkResY) + chunkResY/2, 100);
} }
} }
} }
@@ -84,72 +84,91 @@ public class Level {
int rainDrops = (int)Math.Round(rainAmount); int rainDrops = (int)Math.Round(rainAmount);
for (int i = 0; i <= rainDrops; i++) { for (int i = 0; i <= rainDrops; i++) {
if (GD.Randf() < rainAmount) if (GD.Randf() < rainAmount) {
WritePixel<Water>((int)(GD.Randi() % (chunkResX * chunksPerX)), 0, 1); int d = (int)(GD.Randi() % (chunkResX * chunksPerX));
if (GetElement(d, 0).IsEmpty())
WritePixel<Water>(d, 0, 1);
}
} }
} }
public void WritePixel<T>(int x, int y, int size) { public void WritePixel<T>(int x, int y, int size) {
int halfsize = size/2; int halfsize = size/2;
int startPtX = x % chunkResX;
int startPtY = y % chunkResY;
for (int i = -halfsize; i <= halfsize; i++) { for (int i = -halfsize; i <= halfsize; i++) {
for (int j = 0; j < 1; j++) { for (int j = -halfsize; j <= halfsize; j++) {
// for (int j = -halfsize; j <= halfsize; j++) {
// calculate in-chunk coordinates // calculate in-chunk coordinates
int inChunkX = x % chunkResX; int iteratorX = i;
int inChunkY = y % chunkResY; int iteratorY = j;
int brushX = i; int ptX = startPtX;
int brushY = j; int ptY = startPtY;
// calculate chunk to write to // calculate chunk to write to
int chunkX = x/chunkResX; int chunkX = x/chunkResX;
if (inChunkX + i < 0) {
// left of chunk
if (startPtX + iteratorX < 0) {
chunkX--; chunkX--;
// 320 -22 iteratorX = chunkResX + startPtX + iteratorX;
brushX = chunkResX + inChunkX + i; ptX = startPtX + iteratorX;
inChunkX = chunkResX - inChunkX;
} else if (inChunkX + i >= chunkResX) { } else {
ptX = startPtX + iteratorX;
}
if (startPtX + iteratorX >= chunkResX) {
chunkX++; chunkX++;
inChunkX = 0; ptX = startPtX + iteratorX - chunkResX;
brushX = i % chunkResX;
} }
inChunkX += brushX;
if (chunkX < 0 || chunkX >= chunksPerX) {
GD.PrintErr($"Trying to write out of bounds: {x}:{y}");
return;
}
int chunkY = y/chunkResY; int chunkY = y/chunkResY;
if (inChunkY + j < 0) {
// above chunk
if (startPtY + iteratorY < 0) {
chunkY--; chunkY--;
brushY = chunkResY - (inChunkY + j); ptY = chunkResY + (startPtY + iteratorY);
inChunkY = chunkResY - inChunkY; } else {
} else if (inChunkY + j >= chunkResY) { ptY = startPtY + iteratorY;
}
if (startPtY + iteratorY >= chunkResY) {
chunkY++; chunkY++;
inChunkY = 0; ptY = startPtY + iteratorY - chunkResY;
brushY = j % chunkResY;
}
inChunkY += brushY;
if (chunkY < 0 || chunkY >= chunksPerY) {
GD.PrintErr($"Trying to write out of bounds: {x}:{y}");
return;
} }
chunks[chunkX, chunkY].WritePixel<T>(inChunkX, inChunkY); // ignore everything outside
if (chunkX < 0) continue;
if (chunkX >= chunksPerX) continue;
if (chunkY < 0) continue;
if (chunkY >= chunksPerY) continue;
chunks[chunkX, chunkY].WritePixel<T>(ptX, ptY);
} }
} }
} }
public Element GetElement(int x, int y) {
if (x < 0 || x > chunkResX * chunkResX) return null;
if (y < 0 || y > chunkResY * chunkResY) return null;
return chunks[x / chunkResX, y / chunkResY].Elements[x % chunkResX, y % chunkResY];
}
public Image DrawLevel() { public Image DrawLevel() {
// chunk
for (int cx = 0; cx < chunksPerX; cx++) { for (int cx = 0; cx < chunksPerX; cx++) {
for (int cy = 0; cy < chunksPerY; cy++) { for (int cy = 0; cy < chunksPerY; cy++) {
// pixel in chunk
for (int x = 0; x < chunkResX; x++) { for (int x = 0; x < chunkResX; x++) {
for (int y = 0; y < chunkResY; y++) { for (int y = 0; y < chunkResY; y++) {
// TODO: multithreading here! use Chunk.DrawLevel() and stitch images together // TODO: multithreading here! use Chunk.DrawLevel() and stitch images together
image.SetPixel(cx*chunkResX + x, cy*chunkResY + y, chunks[cx,cy].Elements[x, y].Color);
if (chunks[cx, cy].Elements[x, y].IsMarkedForUpdate()) {
image.SetPixel(cx * chunkResX + x, cy * chunkResY + y, chunks[cx, cy].Elements[x, y].Color);
chunks[cx, cy].Elements[x, y].MarkForUpdate(false);
}
} }
} }
} }

View File

@@ -3,19 +3,27 @@ using FOU.Scripts.Elements;
using Godot; using Godot;
public partial class Main : Node2D { public partial class Main : Node2D {
[Export] public bool DebugVisualization = false; [Export] public bool DebugMode = false;
[Export] public bool AllowOverwrite = true;
[Export] public int BrushSize = 5; [Export] public int BrushSize = 5;
[Export] public float TextureResolution = 0.5f; [Export] public float TextureResolution = 0.5f;
[Export] public int ChunksPerAxis = 2; [Export] public int ChunksPerAxis = 2;
[Export] public float RainAmount = 0; [Export] public float rainAmount = 0;
public static Main Instance; public static Main Instance;
public Level Level; public Level Level;
private TextureRect mLevelDrawer; private TextureRect mLevelDrawer;
private bool enableRain; private bool enableRain;
private float rainAmount;
public float RainAmount {
get => rainAmount;
set {
rainAmount = value;
Level?.SetRainAmount(rainAmount);
}
}
public override void _Ready() { public override void _Ready() {
Level = new Level(this, (int)(GetViewportRect().Size.X * TextureResolution), Level = new Level(this, (int)(GetViewportRect().Size.X * TextureResolution),
@@ -28,10 +36,11 @@ public partial class Main : Node2D {
public override void _PhysicsProcess(double delta) { public override void _PhysicsProcess(double delta) {
base._PhysicsProcess(delta); base._PhysicsProcess(delta);
Level.Update();
} }
public override void _Process(double delta) { public override void _Process(double delta) {
Level.Update();
mLevelDrawer.Texture?.Dispose();
mLevelDrawer.Texture = ImageTexture.CreateFromImage(Level.DrawLevel()); mLevelDrawer.Texture = ImageTexture.CreateFromImage(Level.DrawLevel());
} }