I am trying to make a rudimentary, very simple animation function using a simple manager. The issue is that, as of right now, when the user clicks "Go" on the main form, it will display the first image then jump to the last image.
I have also tried to insert a delay which delays the thing going from 1 to the last frame but it just jumps to the last frame. I'm utilizing several PNG files in order to make this thing work instead of trying to use a "sprite sheet" which would be a larger pain in the arse I would imageine...
Here's the code for the AnimationManager class...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
namespace Animation
{
public class AnimationManager
{
public PictureBox AnimationBox;
public float widthX, heightY;
public string ImageDirectory, ImageName;
public int frame, maxFrames;
public int speed; // in milliseconds
public bool isLooping;
public AnimationManager()
{
AnimationBox = new PictureBox();
widthX = 0;
heightY = 0;
ImageName = null;
frame = 1;
speed = 0;
maxFrames = 0;
isLooping = false;
}
public async void CycleThrough(string ImageDirectory, int FPMS, int maxFrames, bool isLooping)
{
for (int i = frame; i < maxFrames + 1; i++)
{
ImageName = ImageDirectory + #"\" + frame.ToString() + ".png";
AnimationBox.Image = Image.FromFile(ImageName);
AnimationBox.Refresh();
await Task.Delay(FPMS);
switch (isLooping)
{
case false:
default:
{
frame = maxFrames;
break;
}
case true:
{
frame = 1;
break;
}
}
}
}
}
}
That's as far as I was able to get. If anyone has any insight or can point me in the right direction to get this to work, that would be awesome.
You had some problems in your code so i write it again and i comment out where you had problem
public async void CycleThrough(string ImageDirectory, int FPS, int maxFrames, bool isLooping)
{
for (int i = frame; i < maxFrames + 1; i++) // frame and maxFrames must not change. counter is i
{
ImageName = ImageDirectory + #"\" + i.ToString() + ".png"; // Get the i-th png using counter.
AnimationBox.Image = Image.FromFile(ImageName);
AnimationBox.Refresh();
await Task.Delay(TimeSpan.FromSeconds(1/FPS)); // delay in seconds.
if(isLooping && i == maxframes) // if must Loop and counter is in last iteration then start over
{
i = frame - 1; // set the counter to the first frame
}
}
}
While waiting for the answer, I went ahead and attempted to do it myself... :) Thank you for your answers and comments...
This was my final solution...
private async void CycleThrough(string ImageDirectory, int FPMS, int maxFrames, bool isLooping)
{
this.speed = FPMS;
for (int i = frame; i < maxFrames + 1; i++)
{
ImageName = ImageDirectory + #"\" + i.ToString() + ".png";
Console.WriteLine(frame.ToString());
AnimationBox.Refresh();
AnimationBox.Image = Image.FromFile(ImageName);
await Task.Delay(FPMS);
if (isLooping)
{
if (i >= maxFrames) i = 1;
}
}
}
I made an elementary mistake and didn't use "i" that was setup. Instead I manipulated a bunch of other things that had no bearing on the loop's intent itself.
I agree with the person above - I didn't use a switch in my solution. It didn't make sense since I only have one conditional that would reset the loop (manipulating i back to 1) if isLooping was true.
This works beautifully so I'll offer this solution to anyone who wants a rudimentary animator for their code :)
Thanks all for your answers and time.
Related
When building a Windows Console App in C#, is it possible to write to the console without having to extend a current line or go to a new line? For example, if I want to show a percentage representing how close a process is to completion, I'd just like to update the value on the same line as the cursor, and not have to put each percentage on a new line.
Can this be done with a "standard" C# console app?
If you print only "\r" to the console the cursor goes back to the beginning of the current line and then you can rewrite it. This should do the trick:
for(int i = 0; i < 100; ++i)
{
Console.Write("\r{0}% ", i);
}
Notice the few spaces after the number to make sure that whatever was there before is erased.
Also notice the use of Write() instead of WriteLine() since you don't want to add an "\n" at the end of the line.
You can use Console.SetCursorPosition to set the position of the cursor and then write at the current position.
Here is an example showing a simple "spinner":
static void Main(string[] args)
{
var spin = new ConsoleSpinner();
Console.Write("Working....");
while (true)
{
spin.Turn();
}
}
public class ConsoleSpinner
{
int counter;
public void Turn()
{
counter++;
switch (counter % 4)
{
case 0: Console.Write("/"); counter = 0; break;
case 1: Console.Write("-"); break;
case 2: Console.Write("\\"); break;
case 3: Console.Write("|"); break;
}
Thread.Sleep(100);
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
}
}
Note that you will have to make sure to overwrite any existing output with new output or blanks.
Update: As it has been criticized that the example moves the cursor only back by one character, I will add this for clarification: Using SetCursorPosition you may set the cursor to any position in the console window.
Console.SetCursorPosition(0, Console.CursorTop);
will set the cursor to the beginning of the current line (or you can use Console.CursorLeft = 0 directly).
So far we have three competing alternatives for how to do this:
Console.Write("\r{0} ", value); // Option 1: carriage return
Console.Write("\b\b\b\b\b{0}", value); // Option 2: backspace
{ // Option 3 in two parts:
Console.SetCursorPosition(0, Console.CursorTop); // - Move cursor
Console.Write(value); // - Rewrite
}
I've always used Console.CursorLeft = 0, a variation on the third option, so I decided to do some tests. Here's the code I used:
public static void CursorTest()
{
int testsize = 1000000;
Console.WriteLine("Testing cursor position");
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < testsize; i++)
{
Console.Write("\rCounting: {0} ", i);
}
sw.Stop();
Console.WriteLine("\nTime using \\r: {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
int top = Console.CursorTop;
for (int i = 0; i < testsize; i++)
{
Console.SetCursorPosition(0, top);
Console.Write("Counting: {0} ", i);
}
sw.Stop();
Console.WriteLine("\nTime using CursorLeft: {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
Console.Write("Counting: ");
for (int i = 0; i < testsize; i++)
{
Console.Write("\b\b\b\b\b\b\b\b{0,8}", i);
}
sw.Stop();
Console.WriteLine("\nTime using \\b: {0}", sw.ElapsedMilliseconds);
}
On my machine, I get the following results:
Backspaces: 25.0 seconds
Carriage Returns: 28.7 seconds
SetCursorPosition: 49.7 seconds
Additionally, SetCursorPosition caused noticeable flicker that I didn't observe with either of the alternatives. So, the moral is to use backspaces or carriage returns when possible, and thanks for teaching me a faster way to do this, SO!
Update: In the comments, Joel suggests that SetCursorPosition is constant with respect to the distance moved while the other methods are linear. Further testing confirms that this is the case, however constant time and slow is still slow. In my tests, writing a long string of backspaces to the console is faster than SetCursorPosition until somewhere around 60 characters. So backspace is faster for replacing portions of the line shorter than 60 characters (or so), and it doesn't flicker, so I'm going to stand by my initial endorsement of \b over \r and SetCursorPosition.
You can use the \b (backspace) escape sequence to backup a particular number of characters on the current line. This just moves the current location, it does not remove the characters.
For example:
string line="";
for(int i=0; i<100; i++)
{
string backup=new string('\b',line.Length);
Console.Write(backup);
line=string.Format("{0}%",i);
Console.Write(line);
}
Here, line is the percentage line to write to the console. The trick is to generate the correct number of \b characters for the previous output.
The advantage of this over the \r approach is that if works even if your percentage output is not at the beginning of the line.
\r is used for these scenarios.
\r represents a carriage return which means the cursor returns to the start of the line.
That's why Windows uses \n\r as its new line marker.
\n moves you down a line, and \r returns you to the start of the line.
I just had to play with the divo's ConsoleSpinner class. Mine is nowhere near as concise, but it just didn't sit well with me that users of that class have to write their own while(true) loop. I'm shooting for an experience more like this:
static void Main(string[] args)
{
Console.Write("Working....");
ConsoleSpinner spin = new ConsoleSpinner();
spin.Start();
// Do some work...
spin.Stop();
}
And I realized it with the code below. Since I don't want my Start() method to block, I don't want the user to have to worry about writing a while(spinFlag) -like loop, and I want to allow multiple spinners at the same time I had to spawn a separate thread to handle the spinning. And that means the code has to be a lot more complicated.
Also, I haven't done that much multi-threading so it's possible (likely even) that I've left a subtle bug or three in there. But it seems to work pretty well so far:
public class ConsoleSpinner : IDisposable
{
public ConsoleSpinner()
{
CursorLeft = Console.CursorLeft;
CursorTop = Console.CursorTop;
}
public ConsoleSpinner(bool start)
: this()
{
if (start) Start();
}
public void Start()
{
// prevent two conflicting Start() calls ot the same instance
lock (instanceLocker)
{
if (!running )
{
running = true;
turner = new Thread(Turn);
turner.Start();
}
}
}
public void StartHere()
{
SetPosition();
Start();
}
public void Stop()
{
lock (instanceLocker)
{
if (!running) return;
running = false;
if (! turner.Join(250))
turner.Abort();
}
}
public void SetPosition()
{
SetPosition(Console.CursorLeft, Console.CursorTop);
}
public void SetPosition(int left, int top)
{
bool wasRunning;
//prevent other start/stops during move
lock (instanceLocker)
{
wasRunning = running;
Stop();
CursorLeft = left;
CursorTop = top;
if (wasRunning) Start();
}
}
public bool IsSpinning { get { return running;} }
/* --- PRIVATE --- */
private int counter=-1;
private Thread turner;
private bool running = false;
private int rate = 100;
private int CursorLeft;
private int CursorTop;
private Object instanceLocker = new Object();
private static Object console = new Object();
private void Turn()
{
while (running)
{
counter++;
// prevent two instances from overlapping cursor position updates
// weird things can still happen if the main ui thread moves the cursor during an update and context switch
lock (console)
{
int OldLeft = Console.CursorLeft;
int OldTop = Console.CursorTop;
Console.SetCursorPosition(CursorLeft, CursorTop);
switch (counter)
{
case 0: Console.Write("/"); break;
case 1: Console.Write("-"); break;
case 2: Console.Write("\\"); break;
case 3: Console.Write("|"); counter = -1; break;
}
Console.SetCursorPosition(OldLeft, OldTop);
}
Thread.Sleep(rate);
}
lock (console)
{ // clean up
int OldLeft = Console.CursorLeft;
int OldTop = Console.CursorTop;
Console.SetCursorPosition(CursorLeft, CursorTop);
Console.Write(' ');
Console.SetCursorPosition(OldLeft, OldTop);
}
}
public void Dispose()
{
Stop();
}
}
Explicitly using a Carrage Return (\r) at the beginning of the line rather than (implicitly or explicitly) using a New Line (\n) at the end should get what you want. For example:
void demoPercentDone() {
for(int i = 0; i < 100; i++) {
System.Console.Write( "\rProcessing {0}%...", i );
System.Threading.Thread.Sleep( 1000 );
}
System.Console.WriteLine();
}
public void Update(string data)
{
Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
Console.Write(string.Format("\r{0}", data));
}
From the Console docs in MSDN:
You can solve this problem by setting
the TextWriter.NewLine property of the
Out or Error property to another line
termination string. For example, the
C# statement, Console.Error.NewLine =
"\r\n\r\n";, sets the line termination
string for the standard error output
stream to two carriage return and line
feed sequences. Then you can
explicitly call the WriteLine method
of the error output stream object, as
in the C# statement,
Console.Error.WriteLine();
So - I did this:
Console.Out.Newline = String.Empty;
Then I am able to control the output myself;
Console.WriteLine("Starting item 1:");
Item1();
Console.WriteLine("OK.\nStarting Item2:");
Another way of getting there.
This works if you want to make generating files look cool .
int num = 1;
var spin = new ConsoleSpinner();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("");
while (true)
{
spin.Turn();
Console.Write("\r{0} Generating Files ", num);
num++;
}
And this is the method that i got from some answer below and modified it
public class ConsoleSpinner
{
int counter;
public void Turn()
{
counter++;
switch (counter % 4)
{
case 0: Console.Write("."); counter = 0; break;
case 1: Console.Write(".."); break;
case 2: Console.Write("..."); break;
case 3: Console.Write("...."); break;
case 4: Console.Write("\r"); break;
}
Thread.Sleep(100);
Console.SetCursorPosition(23, Console.CursorTop);
}
}
Here's another one :D
class Program
{
static void Main(string[] args)
{
Console.Write("Working... ");
int spinIndex = 0;
while (true)
{
// obfuscate FTW! Let's hope overflow is disabled or testers are impatient
Console.Write("\b" + #"/-\|"[(spinIndex++) & 3]);
}
}
}
If you want update one line, but the information is too long to show on one line, it may need some new lines. I've encountered this problem, and below is one way to solve this.
public class DumpOutPutInforInSameLine
{
//content show in how many lines
int TotalLine = 0;
//start cursor line
int cursorTop = 0;
// use to set character number show in one line
int OneLineCharNum = 75;
public void DumpInformation(string content)
{
OutPutInSameLine(content);
SetBackSpace();
}
static void backspace(int n)
{
for (var i = 0; i < n; ++i)
Console.Write("\b \b");
}
public void SetBackSpace()
{
if (TotalLine == 0)
{
backspace(OneLineCharNum);
}
else
{
TotalLine--;
while (TotalLine >= 0)
{
backspace(OneLineCharNum);
TotalLine--;
if (TotalLine >= 0)
{
Console.SetCursorPosition(OneLineCharNum, cursorTop + TotalLine);
}
}
}
}
private void OutPutInSameLine(string content)
{
//Console.WriteLine(TotalNum);
cursorTop = Console.CursorTop;
TotalLine = content.Length / OneLineCharNum;
if (content.Length % OneLineCharNum > 0)
{
TotalLine++;
}
if (TotalLine == 0)
{
Console.Write("{0}", content);
return;
}
int i = 0;
while (i < TotalLine)
{
int cNum = i * OneLineCharNum;
if (i < TotalLine - 1)
{
Console.WriteLine("{0}", content.Substring(cNum, OneLineCharNum));
}
else
{
Console.Write("{0}", content.Substring(cNum, content.Length - cNum));
}
i++;
}
}
}
class Program
{
static void Main(string[] args)
{
DumpOutPutInforInSameLine outPutInSameLine = new DumpOutPutInforInSameLine();
outPutInSameLine.DumpInformation("");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
//need several lines
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbb");
}
}
i was looking for same solution in vb.net and i found this one and it's great.
however as #JohnOdom suggested a better way to handle the blanks space if previous one is larger than current one..
i make a function in vb.net and thought someone could get helped ..
here is my code:
Private Sub sPrintStatus(strTextToPrint As String, Optional boolIsNewLine As Boolean = False)
REM intLastLength is declared as public variable on global scope like below
REM intLastLength As Integer
If boolIsNewLine = True Then
intLastLength = 0
End If
If intLastLength > strTextToPrint.Length Then
Console.Write(Convert.ToChar(13) & strTextToPrint.PadRight(strTextToPrint.Length + (intLastLength - strTextToPrint.Length), Convert.ToChar(" ")))
Else
Console.Write(Convert.ToChar(13) & strTextToPrint)
End If
intLastLength = strTextToPrint.Length
End Sub
I was doing a search for this to see if the solution I wrote could be optimised for speed. What I wanted was a countdown timer, not just updating the current line.
Here's what I came up with. Might be useful to someone
int sleepTime = 5 * 60; // 5 minutes
for (int secondsRemaining = sleepTime; secondsRemaining > 0; secondsRemaining --)
{
double minutesPrecise = secondsRemaining / 60;
double minutesRounded = Math.Round(minutesPrecise, 0);
int seconds = Convert.ToInt32((minutesRounded * 60) - secondsRemaining);
Console.Write($"\rProcess will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)} ");
Thread.Sleep(1000);
}
Console.WriteLine("");
Inspired by #E.Lahu Solution, implementation of a bar progress with percentage.
public class ConsoleSpinner
{
private int _counter;
public void Turn(Color color, int max, string prefix = "Completed", string symbol = "■",int position = 0)
{
Console.SetCursorPosition(0, position);
Console.Write($"{prefix} {ComputeSpinner(_counter, max, symbol)}", color);
_counter = _counter == max ? 0 : _counter + 1;
}
public string ComputeSpinner(int nmb, int max, string symbol)
{
var spinner = new StringBuilder();
if (nmb == 0)
return "\r ";
spinner.Append($"[{nmb}%] [");
for (var i = 0; i < max; i++)
{
spinner.Append(i < nmb ? symbol : ".");
}
spinner.Append("]");
return spinner.ToString();
}
}
public static void Main(string[] args)
{
var progressBar= new ConsoleSpinner();
for (int i = 0; i < 1000; i++)
{
progressBar.Turn(Color.Aqua,100);
Thread.Sleep(1000);
}
}
Here is my take on s soosh's and 0xA3's answers.
It can update the console with user messages while updating the spinner and has an elapsed time indicator aswell.
public class ConsoleSpiner : IDisposable
{
private static readonly string INDICATOR = "/-\\|";
private static readonly string MASK = "\r{0} {1:c} {2}";
int counter;
Timer timer;
string message;
public ConsoleSpiner() {
counter = 0;
timer = new Timer(200);
timer.Elapsed += TimerTick;
}
public void Start() {
timer.Start();
}
public void Stop() {
timer.Stop();
counter = 0;
}
public string Message {
get { return message; }
set { message = value; }
}
private void TimerTick(object sender, ElapsedEventArgs e) {
Turn();
}
private void Turn() {
counter++;
var elapsed = TimeSpan.FromMilliseconds(counter * 200);
Console.Write(MASK, INDICATOR[counter % 4], elapsed, this.Message);
}
public void Dispose() {
Stop();
timer.Elapsed -= TimerTick;
this.timer.Dispose();
}
}
usage is something like this:
class Program
{
static void Main(string[] args)
{
using (var spinner = new ConsoleSpiner())
{
spinner.Start();
spinner.Message = "About to do some heavy staff :-)"
DoWork();
spinner.Message = "Now processing other staff".
OtherWork();
spinner.Stop();
}
Console.WriteLine("COMPLETED!!!!!\nPress any key to exit.");
}
}
The SetCursorPosition method works in multi-threading scenario, where the other two methods don't
I have this counter programm based on object oriented programming in C# and
the counter has an Increment Method which counts up so like it goes from 0..1...2.. to 30.
The for loop was working perfectly fine but then I decided to add a class with two Methods that either count up in odd or even steps which also works fine. The only problem is that after changing the main class now it doesn't work in a loop anymore until its on 30, so I have to enter every step individually.
This is my main code (I'm pretty sure the problem is due to the Console.ReadLine and input in general but I don't know what exactly is the issue)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Counter
{
class Program
{
static void Main(string[] args)
{
char confirm;
int input;
do
{
//int inNum;
Console.Clear();
ModusCounter b = new ModusCounter(0, 30);
for (; ; )
{
b.Display(19, 7);
Console.Write("do you want (1)odd // (2) even // (0)standard ? ");
input = Convert.ToInt32(Console.ReadLine());
if (input == 1)
{
b.OddCount();
}
else if (input == 2)
{
b.EvenCount();
}
else
b.Increment();
System.Threading.Thread.Sleep(300);
if (Console.KeyAvailable) break;
}
//b.Increment();
b.Display(19, 7);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("do you want to restart? (J // N) ");
confirm = Convert.ToChar(Console.ReadLine());
}
while (confirm == 'J');
Console.ReadLine();
}
}
}
ModusCounter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Counter
{
public class ModusCounter:CCounter
{
private int wholenum;
public int Wholenum
{
get => wholenum;
set
{
wholenum = value;
}
}
public ModusCounter(int start, int limit) : base(start, limit)
{ }
public void EvenCount() //2
{
Wholenum = 2;
Level += Wholenum;
if (Level % 2 == 0)
{
Level += Wholenum - 2;
}
else
{
Level += Wholenum -1;
}
if (Level > Limit)
Level = Start;
}
public void OddCount() //1
{
Wholenum = 1;
Level += Wholenum;
if (Level % 2 == 0)
{
Level += Wholenum;
}
else
{
Level += Wholenum + 1;
}
if (Level > Limit)
Level = Start;
}
}
}
base class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Counter
{
public class CCounter
{
private int start;
private int limit;
private int level;
public int Start
{
get => start;
protected set
{
start = value;
}
}
public int Limit
{
get => limit;
protected set
{
limit = value;
}
}
public int Level
{
get => level;
protected set
{
if (value < Start)
level = Start;
else if (value > Limit)
Level = Limit;
else
level = value;
}
}
public CCounter(int start_, int limit_)
{
Start = start_;
Limit = limit_;
Level = Start;
}
public void Increment()
{
int level = Level + 1;
if (level > Limit9
Level = Start;
else
Level = level;
}
public void Display(int x_, int y_)
{
Console.SetCursorPosition(x_, y_);
Console.Write(Level.ToString("00"));
}
}
}
First, welcome to S/O. Hope you get good info out here during your obvious learning curve into development. A couple things could get simplified, but that will come with learning, so I wont pounce, but you will see as your skills improve over time.
First, your for(;;) loop. That is in-itself causing an infinite loop. The console read line to get input is always waiting for the next option from your prompt asking for odd, even or standard, so it keeps cycling through.
I dont think your Console.KeyAvailable is ever really getting triggered to break out of the for loop to get you to the next prompt of allowing the user to retry J/N option. You have the console.ReadLine which reads the entire buffer until you hit the enter key. By that time, you are incrementing your cycle, displaying the results and giving a 300 millisecond delay from your Thread.Sleep(300) call, of which you are probably not clicking a key in that short span to break the loop. Now, if you changed the Thread.Sleep(2000) you would be giving the user 2 seconds (1 second = 1000 milliseconds) AFTER the display to press any key to break the loop.
Next item/question I have for you. Your incrementing you appear to be ok with that functionality, but what / why do you want to break. When it gets to the limit? If so, then test for that after the increment process has completed such as (before the Thread.Sleep call)
if (b.Level >= b.Limit)
break;
Getting to your CCounter class and looking at its Increment method. You have a check that if the local "level" variable is greater than the limit to set the level = START value. Was that intended, or not. Also, by using a new int value by the same name as a property on your class (int level vs property level) is not a good practice and can add confusion.
public void Increment()
{
int level = Level + 1;
if (level > Limit)
Level = Start;
else
Level = level;
}
It appears you are trying to use a local temporary variable but is not necessary. It could just as easily be done with slight alteration as I have below.
public void Increment()
{
if (Level +1 > Limit)
Level = Start;
else
Level = Level +1;
}
Because I am not ASSIGNING a value to the "Level" public property, it in essence is doing the +1 before its COMPARISON to the LIMIT property. Only in the ELSE condition do I assign.
Also, a shortcut on adding to a variable or subtracting could be seen as
Level++
or
Level--
Hopefully this helps add SOME clarity to what you are looking to get resolution to and can get you further in your development.
In the file below, I use the video that you can find here to make a type-writer effect on screen. I'm having a bit of an issue with it at this time.
For whatever reason, it's cutting off the last letter whenever I use it (i.e. putting in "Hello there" types out "Hello ther"). Any ideas on why this is happening?
The programming I am using is a modified version to fit my game:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class TypeWriterEffect : MonoBehaviour {
public float delay = 0.1f;
public float startDelay = 0f;
public string fullText;
public bool showGameHS;
public bool showTotalScore;
public string totalGameScore;
private string currentText = "";
// Use this for initialization
void Start () {
totalGameScore = PlayerPrefs.GetString("totalGameScore"); // total score throughout the game
if (showTotalScore)
{
fullText = TimerScript.fullScore.ToString() + "."; // a "." is added to fix this issue
}
else if (showGameHS) // the local highscore
{
fullText = totalGameScore + "."; // a "." is added to fix this issue
}
StartCoroutine(ShowText());
}
IEnumerator ShowText(){
yield return new WaitForSeconds(startDelay); // this was added on as a basic start delay
for (int i = 0; i < fullText.Length; i++){
currentText = fullText.Substring(0,i);
this.GetComponent<Text>().text = currentText;
yield return new WaitForSeconds(delay);
}
}
}
Your for loop is looping from 0 to Length - 1. Then you are using this variable to tell Substring the number of characters to return. It returns 0 characters the first time. At the end, it returns all but the last character.
You can either add 1 to the length argument, or change the bounds of your for loop:
for (int length = 1; length <= fullText.Length; length++){
currentText = fullText.Substring(0, length);
this.GetComponent<Text>().text = currentText;
yield return new WaitForSeconds(delay);
}
I want to play a video (mostly .mov with Motion JPEG) in frame by frame mode with changing framerate. I have a function who gives me a framenumber and then I have to jump there. It will be mostly in one direction but can skip a few frames from time to time; also the velocity is not constant.
So I have a timer asking every 40ms about a new framenumber and setting the new position.
My first approach now is with DirectShow.Net (Interop.QuartzTypeLib). Therefore I render and open the video and set it to pause to draw the picture in the graph
FilgraphManagerClass media = new FilgraphManagerClass();
media.RenderFile(FileName);
media.pause();
Now I will just set a new position
media.CurrentPosition = framenumber * media.AvgTimePerFrame;
Since the video is in pause mode it will then draw every requested new position (frame). Works perfectly fine but really slow... the video keeps stuttering and lagging and its not the video source; there are enough frames recorded to play a fluent video.
With some performance tests I found out that the LAV-Codec is the bottleneck here. This is not included directly in my project since its a DirectShow-Player it will be cast through my codec pack I installed on my PC.
Ideas:
Using the LAV-Codec by myself directly in C#. I searched but everyone is using DirectShow it seems, building their own filters and not using existing ones directly in the project.
Instead of seeking or setting the time, can I get single frames just by the framenumber and draw them simply?
Is there a complete other way to archive what I want to do?
Background:
This project has to be a train simulator. We recorded real time videos of trains driving from inside the cockpit and know which frame is what position. Now my C# programm calculates the position of the train in dependence of time and acceleration, gives back the appropriate framenumber and draw this frame.
Additional Information:
There is another project (not written by me) in C/C++ who uses DirectShow and the avcodec-LAV directly with a similar way I do and it works fine! Thats because I had the idea to use a codec / filter like the avrcodec-lav by myself. But I can't find an interop or interface to work with C#.
Thanks everyone for reading this and trying to help! :)
Obtaining specific frame by seeking filter graph (the entire pipeline) is pretty slow since every seek operation involves the following on its backyard: flushing everything, possibly re-creating worker threads, seeking to first key frame/splice point/clean point/I-Frame before the requested time, start of decoding starting from found position skipping frames until originally requested time is reached.
Overall, the method works well when you scrub paused video, or retrieve specific still frames. When however you try to play this as smooth video, it eventually causes significant part of the effort to be wasted and spent on seeking within video stream.
Solutions here are:
re-encode video to remove or reduce temporal compression (e.g. Motion JPEG AVI/MOV/MP4 files)
whenever possible prefer to skip frames and/or re-timestamp them according to your algorithm instead of seeking
have a cached of decoded video frames and pick from there, populate them as necessary in worker thread
The latter two are unfortunately hard to achieve without advanced filter development (where continuous decoding without interruption by seeking operations is the key to achieving decent performance). With basic DirectShow.Net you only have basic control over streaming and hence the first item from the list above.
Wanted to post a comment instead of an answer, but don't have the reputation. I think your heading in the wrong direction with Direct Show. I've been messing with motion-jpeg for a few years now between C# & Android, and have gotten great performance with built-in .NET code (for converting byte-array to Jpeg frame) and a bit of multi-threading. I can easily achieve over 30fps from multiple devices with each device running in it's own thread.
Below is an older version of my motion-jpeg parser from my C# app 'OmniView'. To use, just send the network stream to the constructor, and receive the OnImageReceived event. Then you can easily save the frames to the hard-drive for later use (perhaps with the filename set to the timestamp for easy lookup). For better performance though, you will want to save all of the images to one file.
using OmniView.Framework.Helpers;
using System;
using System.IO;
using System.Text;
using System.Windows.Media.Imaging;
namespace OmniView.Framework.Devices.MJpeg
{
public class MJpegStream : IDisposable
{
private const int BUFFER_SIZE = 4096;
private const string tag_length = "Content-Length:";
private const string stamp_format = "yyyyMMddHHmmssfff";
public delegate void ImageReceivedEvent(BitmapImage img);
public delegate void FrameCountEvent(long frames, long failed);
public event ImageReceivedEvent OnImageReceived;
public event FrameCountEvent OnFrameCount;
private bool isHead, isSetup;
private byte[] buffer, newline, newline_src;
private int imgBufferStart;
private Stream data_stream;
private MemoryStream imgStreamA, imgStreamB;
private int headStart, headStop;
private long imgSize, imgSizeTgt;
private bool useStreamB;
public volatile bool EnableRecording, EnableSnapshot;
public string RecordPath, SnapshotFilename;
private string boundary_tag;
private bool tagReadStarted;
private bool enableBoundary;
public volatile bool OututFrameCount;
private long FrameCount, FailedCount;
public MJpegStream() {
isSetup = false;
imgStreamA = new MemoryStream();
imgStreamB = new MemoryStream();
buffer = new byte[BUFFER_SIZE];
newline_src = new byte[] {13, 10};
}
public void Init(Stream stream) {
this.data_stream = stream;
FrameCount = FailedCount = 0;
startHeader(0);
}
public void Dispose() {
if (data_stream != null) data_stream.Dispose();
if (imgStreamA != null) imgStreamA.Dispose();
if (imgStreamB != null) imgStreamB.Dispose();
}
//=============================
public void Process() {
if (isHead) processHeader();
else {
if (enableBoundary) processImageBoundary();
else processImage();
}
}
public void Snapshot(string filename) {
SnapshotFilename = filename;
EnableSnapshot = true;
}
//-----------------------------
// Header
private void startHeader(int remaining_bytes) {
isHead = true;
headStart = 0;
headStop = remaining_bytes;
imgSizeTgt = 0;
tagReadStarted = false;
}
private void processHeader() {
int t = BUFFER_SIZE - headStop;
headStop += data_stream.Read(buffer, headStop, t);
int nl;
//
if (!isSetup) {
byte[] new_newline;
if ((nl = findNewline(headStart, headStop, out new_newline)) >= 0) {
string tag = Encoding.UTF8.GetString(buffer, headStart, nl - headStart);
if (tag.StartsWith("--")) boundary_tag = tag;
headStart = nl+new_newline.Length;
newline = new_newline;
isSetup = true;
return;
}
} else {
while ((nl = findData(newline, headStart, headStop)) >= 0) {
string tag = Encoding.UTF8.GetString(buffer, headStart, nl - headStart);
if (!tagReadStarted && tag.Length > 0) tagReadStarted = true;
headStart = nl+newline.Length;
//
if (!processHeaderData(tag, nl)) return;
}
}
//
if (headStop >= BUFFER_SIZE) {
string data = Encoding.UTF8.GetString(buffer, headStart, headStop - headStart);
throw new Exception("Invalid Header!");
}
}
private bool processHeaderData(string tag, int index) {
if (tag.StartsWith(tag_length)) {
string val = tag.Substring(tag_length.Length);
imgSizeTgt = long.Parse(val);
}
//
if (tag.Length == 0 && tagReadStarted) {
if (imgSizeTgt > 0) {
finishHeader(false);
return false;
}
if (boundary_tag != null) {
finishHeader(true);
return false;
}
}
//
return true;
}
private void finishHeader(bool enable_boundary) {
int s = shiftBytes(headStart, headStop);
enableBoundary = enable_boundary;
startImage(s);
}
//-----------------------------
// Image
private void startImage(int remaining_bytes) {
isHead = false;
imgBufferStart = remaining_bytes;
Stream imgStream = getStream();
imgStream.Seek(0, SeekOrigin.Begin);
imgStream.SetLength(imgSizeTgt);
imgSize = 0;
}
private void processImage() {
long img_r = (imgSizeTgt - imgSize - imgBufferStart);
int bfr_r = Math.Max(BUFFER_SIZE - imgBufferStart, 0);
int t = (int)Math.Min(img_r, bfr_r);
int s = data_stream.Read(buffer, imgBufferStart, t);
int x = imgBufferStart + s;
appendImageData(0, x);
imgBufferStart = 0;
//
if (imgSize >= imgSizeTgt) processImageData(0);
}
private void processImageBoundary() {
int t = Math.Max(BUFFER_SIZE - imgBufferStart, 0);
int s = data_stream.Read(buffer, imgBufferStart, t);
//
int nl, start = 0;
int end = imgBufferStart + s;
while ((nl = findData(newline, start, end)) >= 0) {
int tag_length = boundary_tag.Length;
if (nl+newline.Length+tag_length > BUFFER_SIZE) {
appendImageData(start, nl+newline.Length - start);
start = nl+newline.Length;
continue;
}
//
string v = Encoding.UTF8.GetString(buffer, nl+newline.Length, tag_length);
if (v == boundary_tag) {
appendImageData(start, nl - start);
int xstart = nl+newline.Length + tag_length;
int xsize = shiftBytes(xstart, end);
processImageData(xsize);
return;
} else {
appendImageData(start, nl+newline.Length - start);
}
start = nl+newline.Length;
}
//
if (start < end) {
int end_x = end - newline.Length;
if (start < end_x) {
appendImageData(start, end_x - start);
}
//
shiftBytes(end - newline.Length, end);
imgBufferStart = newline.Length;
}
}
private void processImageData(int remaining_bytes) {
if (EnableSnapshot) {
EnableSnapshot = false;
saveSnapshot();
}
//
try {
BitmapImage img = createImage();
if (EnableRecording) recordFrame();
if (OnImageReceived != null) OnImageReceived.Invoke(img);
FrameCount++;
}
catch (Exception) {
// output frame error ?!
FailedCount++;
}
//
if (OututFrameCount && OnFrameCount != null) OnFrameCount.Invoke(FrameCount, FailedCount);
//
useStreamB = !useStreamB;
startHeader(remaining_bytes);
}
private void appendImageData(int index, int length) {
Stream imgStream = getStream();
imgStream.Write(buffer, index, length);
imgSize += (length - index);
}
//-----------------------------
private void recordFrame() {
string stamp = DateTime.Now.ToString(stamp_format);
string filename = RecordPath+"\\"+stamp+".jpg";
//
ImageHelper.Save(getStream(), filename);
}
private void saveSnapshot() {
Stream imgStream = getStream();
//
imgStream.Position = 0;
Stream file = File.Open(SnapshotFilename, FileMode.Create, FileAccess.Write);
try {imgStream.CopyTo(file);}
finally {file.Close();}
}
private BitmapImage createImage() {
Stream imgStream = getStream();
imgStream.Position = 0;
return ImageHelper.LoadStream(imgStream);
}
//-----------------------------
private Stream getStream() {return useStreamB ? imgStreamB : imgStreamA;}
private int findNewline(int start, int stop, out byte[] data) {
for (int i = start; i < stop; i++) {
if (i < stop-1 && buffer[i] == newline_src[0] && buffer[i+1] == newline_src[1]) {
data = newline_src;
return i;
} else if (buffer[i] == newline_src[1]) {
data = new byte[] {newline_src[1]};
return i;
}
}
data = null;
return -1;
}
private int findData(byte[] data, int start, int stop) {
int data_size = data.Length;
for (int i = start; i < stop-data_size; i++) {
if (findInnerData(data, i)) return i;
}
return -1;
}
private bool findInnerData(byte[] data, int buffer_index) {
int count = data.Length;
for (int i = 0; i < count; i++) {
if (data[i] != buffer[buffer_index+i]) return false;
}
return true;
}
private int shiftBytes(int start, int end) {
int c = end - start;
for (int i = 0; i < c; i++) {
buffer[i] = buffer[end-c+i];
}
return c;
}
}
}
I am looking to create an external application that monitors the 'FPS' of a DirectX application (like FRAPS without the recording). I have read several Microsoft articles on performance measuring tools - but I am looking to get the feedback (and experience) of the community.
My question: what is the best method for obtaining the FPS of a DirectX application?
Windows has some Event Tracing for Windows providers related to DirectX profiling. The most intresting ones are Microsoft-Windows-D3D9 and Microsoft-Windows-DXGI, which allow tracing of the frame presentation events. The simplest way to calculate FPS is to count the number of PresentStart events withing a time interval and divide that by the length of the interval.
To work with ETW in C#, install Microsoft.Diagnostics.Tracing.TraceEvent package.
The following code sample displays FPS of running processes:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using Microsoft.Diagnostics.Tracing.Session;
namespace ConsoleApp1
{
//helper class to store frame timestamps
public class TimestampCollection
{
const int MAXNUM = 1000;
public string Name { get; set; }
List<long> timestamps = new List<long>(MAXNUM + 1);
object sync = new object();
//add value to the collection
public void Add(long timestamp)
{
lock (sync)
{
timestamps.Add(timestamp);
if (timestamps.Count > MAXNUM) timestamps.RemoveAt(0);
}
}
//get the number of timestamps withing interval
public int QueryCount(long from, long to)
{
int c = 0;
lock (sync)
{
foreach (var ts in timestamps)
{
if (ts >= from && ts <= to) c++;
}
}
return c;
}
}
class Program
{
//event codes (https://github.com/GameTechDev/PresentMon/blob/40ee99f437bc1061a27a2fc16a8993ee8ce4ebb5/PresentData/PresentMonTraceConsumer.cpp)
public const int EventID_D3D9PresentStart = 1;
public const int EventID_DxgiPresentStart = 42;
//ETW provider codes
public static readonly Guid DXGI_provider = Guid.Parse("{CA11C036-0102-4A2D-A6AD-F03CFED5D3C9}");
public static readonly Guid D3D9_provider = Guid.Parse("{783ACA0A-790E-4D7F-8451-AA850511C6B9}");
static TraceEventSession m_EtwSession;
static Dictionary<int, TimestampCollection> frames = new Dictionary<int, TimestampCollection>();
static Stopwatch watch = null;
static object sync = new object();
static void EtwThreadProc()
{
//start tracing
m_EtwSession.Source.Process();
}
static void OutputThreadProc()
{
//console output loop
while (true)
{
long t1, t2;
long dt = 2000;
Console.Clear();
Console.WriteLine(DateTime.Now.ToString() + "." + DateTime.Now.Millisecond.ToString());
Console.WriteLine();
lock (sync)
{
t2 = watch.ElapsedMilliseconds;
t1 = t2 - dt;
foreach (var x in frames.Values)
{
Console.Write(x.Name + ": ");
//get the number of frames
int count = x.QueryCount(t1, t2);
//calculate FPS
Console.WriteLine("{0} FPS", (double)count / dt * 1000.0);
}
}
Console.WriteLine();
Console.WriteLine("Press any key to stop tracing...");
Thread.Sleep(1000);
}
}
public static void Main(string[] argv)
{
//create ETW session and register providers
m_EtwSession = new TraceEventSession("mysess");
m_EtwSession.StopOnDispose = true;
m_EtwSession.EnableProvider("Microsoft-Windows-D3D9");
m_EtwSession.EnableProvider("Microsoft-Windows-DXGI");
//handle event
m_EtwSession.Source.AllEvents += data =>
{
//filter out frame presentation events
if (((int)data.ID == EventID_D3D9PresentStart && data.ProviderGuid == D3D9_provider) ||
((int)data.ID == EventID_DxgiPresentStart && data.ProviderGuid == DXGI_provider))
{
int pid = data.ProcessID;
long t;
lock (sync)
{
t = watch.ElapsedMilliseconds;
//if process is not yet in Dictionary, add it
if (!frames.ContainsKey(pid))
{
frames[pid] = new TimestampCollection();
string name = "";
var proc = Process.GetProcessById(pid);
if (proc != null)
{
using (proc)
{
name = proc.ProcessName;
}
}
else name = pid.ToString();
frames[pid].Name = name;
}
//store frame timestamp in collection
frames[pid].Add(t);
}
}
};
watch = new Stopwatch();
watch.Start();
Thread thETW = new Thread(EtwThreadProc);
thETW.IsBackground = true;
thETW.Start();
Thread thOutput = new Thread(OutputThreadProc);
thOutput.IsBackground = true;
thOutput.Start();
Console.ReadKey();
m_EtwSession.Dispose();
}
}
}
Based on the source code of PresentMon project.
Fraps inserts a DLL into every running application and hooks specific DX calls to figure out the framerate and capture video, pretty sure that you'll have to do something similar. After a bit of poking around I found a Github project that does some basic DX hooking for doing captures and overlays, so that might be a good spot to start out with. Though I've not used it personally so I can't totally vouch for the quality.
http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks/
Building on https://stackoverflow.com/a/54625953/12047161:
I had more success not using the stopwatch as the event triggers seems to be asynchronous with the actual frames. I kept getting batches of 20-50 frames all at once, making the estimated FPS fluctuate between 50 and 250% of the actual value.
Instead i used TimeStampRelativeMSec
//handle event
m_EtwSession.Source.AllEvents += data =>
{
//filter out frame presentation events
if((int) data.ID == EventID_DxgiPresentStart && data.ProviderGuid == DXGI_provider)
{
int pid = data.ProcessID;
long t;
t = watch.ElapsedMilliseconds;
//if process is not yet in Dictionary, add it
if (!frames.ContainsKey(pid))
{
frames[pid] = new TimestampCollection();
string name = "";
var proc = Process.GetProcessById(pid);
if (proc != null)
{
using (proc)
{
name = proc.ProcessName;
}
}
else name = pid.ToString();
frames[pid].Name = name;
}
frames[pid].Add((long)data.TimeStampRelativeMSec);
}
};
property from the TraceEvent class, and calculate FPS by rounding the average time between an arbitrary number of past entries:
public double GetFrameTime(int count)
{
double returnValue = 0;
int listCount = timestamps.Count;
if(listCount > count)
{
for(int i = 1; i <= count; i++)
{
returnValue += timestamps[listCount - i] - timestamps[listCount - (i + 1)];
}
returnValue /= count;
}
return returnValue;
}
This method gave me far more accurate (Compared to, as available, in-game counters) of several different games i've tried.