Reputation: 3
I recently started playing with monogame and to learn about how it works, I wanted to make a visualisation of sorting algorithm.
When I start program the draw() function doesn't redraw the columns at their current state.
It shows the first state and at the end of the loop just jumps to the sorted state.
Am I missing something?
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (Keyboard.GetState().IsKeyDown(Keys.Enter))
{
int temp;
for (int j = 0; j <= MainArray.Length - 2; j++)
{
for (int i = 0; i <= MainArray.Length - 2; i++)
{
if (MainArray[i] > MainArray[i + 1])
{
temp = MainArray[i + 1];
MainArray[i + 1] = MainArray[i];
MainArray[i] = temp;
Draw(gameTime);
Thread.Sleep(300);
}
}
}
}
// TODO: Add your update logic here
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
for (int i = 0; i < MainArray.Length; i++)
{
sprite.Draw(new Vector2(i * 40 + 20, -10), spriteBatch, new Rectangle(i * 40 + 30, 0, 30, MainArray[i] * 8));
}
spriteBatch.End();
// TODO: Add your drawing code here
base.Draw(gameTime);
}
Upvotes: 0
Views: 548
Reputation: 36
Calling Draw()
only adds whatever you ask it to draw in a sort of "draw buffer" that is storing what should be drawn. It is not directly displaying the pixels on the screen.
The operation of displaying the pixels on the screen is done internally by Monogame after calling Update()
. In your code, you are overwriting the "draw buffer" with a new sorting state. When it is time to display the pixels on the screen, Monogame takes whatever is on the "draw buffer" and renders it. That is why you only see the last state.
This is roughly what the inner loop of a Monogame game looks like:
public void Tick()
{
(...)
DoUpdate() // Calls "Update()"
(...)
DoDraw() // Calls "Draw()"
// Nested in some other methods, it also calls "Platform.Present()",
// which is the place where pixels are displayed on the screen
(...)
}
This is what I understood from the source code of Monogame's Game.cs
file.
A way to workaround this issue would be to run the sorting algorithm in another thread, and update the values to be displayed when needed, like this:
protected override void Initialize()
{
latestArray = MainArray;
sortingThread = new Thread(new ThreadStart(Sorting));
sortingThread.IsBackground = true;
sortingThread.Start();
}
private void Sorting()
{
float temp;
for (int j = 0; j <= MainArray.Length - 2; j++) {
for (int i = 0; i <= MainArray.Length - 2; i++) {
if (MainArray[i] > MainArray[i + 1]) {
temp = MainArray[i + 1];
MainArray[i + 1] = MainArray[i];
MainArray[i] = temp;
latestArray = MainArray;
Thread.Sleep(10);
}
}
}
Console.WriteLine("Sorting done !");
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
displayedArray = latestArray;
for (int i = 0; i < displayedArray.Length; i++) {
// Draw column
}
spriteBatch.End();
}
Your sorting works well.
Upvotes: 2
Reputation:
For MonoGame, and game programming in general, you must remember Update() and Draw() are already looped, and called automatically.
You should never call Update() or Draw() in Game1 on your own. Since Draw() does not actually render anything to the screen, you are wasting CPU cycles.
Unlike other programming paradigms, the game loop(Update and Draw) is everything and waits on nothing. While it is possible to break this precept, it should not be done in most cases.
To apply a temporal aspect to a traditional loop it must be done across calls of Update().
When applied to nested loops, the order of the loops inverts since the inner loops are run "faster", or more often, than the outer loops.
A delay mechanism(timer) is implemented to to the loops to slow down the frame changes from the default of 60 fps(16.66667 ms) to a reasonable and visible (~2.9 fps) 300 ms per frame.
There is a bug in your code: Bubble sort requires n * n iterations in the worst case scenario(sorted largest to smallest), your code runs: n(n-1) times, the loop for j terminates one iteration before the threshold is met. I have fixed that problem.
The following code uses these input conventions:
This code also provides an early exit condition that requires a change(swap) to be made each pass, otherwise exit. This code moves the bubble sort complexity closer to n log n
.
Additional needed Game1 class level variables, added below the lines:
public class Game1:Game
{
Please note that the following variable names may not comply with some naming guidelines. Please adjust accordingly.
private int j = 0, i = 0; // move outside since they must carry across calls of Update()
private bool isRunning = false; //Indicate status, stop when paused or done.
private double timer = 0; //accumulator for time in ms
private int timeBetweenFrames = 300; //in ms, when to run next loop iteration
private KeyboardState ks = new KeyboardState(), oks; // needed for key press code
Update()
protected override void Update(GameTime gameTime)
{
oks = ks;
ks = Keyboard.GetState();
if(ks.IsKeyDown(Keys.Escape) Exit();
// new exit code, better due to the single Getstate() call per step/tick/iteration.
if (ks.IsKeyDown(Keys.Enter) && oks.IsKeyUp(Keys.Enter)) // wait for an Enter press
//must be released then pressed before it fires again
// without the additional check this will fire 60 times per second
{
timer = 0;
// i = 0; j = 0; //for reset instead of pause on enter
isRunning = !isRunning;
}
if (isRunning)
{
timer += gameTime.ElapsedGameTime.TotalMilliseconds;
// should be 16.6(1/60) for the default 60 fps
if (timer >= timeBetweenFrames) //wait for timer
{
timer=0; // reset timer and do a loop iteration
//inner loop first, outer is incremented/checked in the else
int temp=-1; // -1 to allow early exit
if(++i <= MainArray.Length - 2)
{
// loop body
if (MainArray[i] > MainArray[i + 1])
{
temp = MainArray[i + 1];
MainArray[i + 1] = MainArray[i];
MainArray[i] = temp;
}
}
else // outer increment and check
{
if(++j >= MainArray.Length || (i < MainArray.Length - 1 && temp == -1))
// corrected bug and provide early exit
// bug in worst case, reverse order source, needs n*n runs
{
//Done
isRunning = false; // stop running
//reset for next run
i = 0;
j = 0;
timer=0;
}
}
}
}
base.Update(gameTime);
}
Upvotes: 0