System.Threading.Timer: Why is it hating me? - c#

I just started messing around with C#/.NET/mono and stuff, and I'm trying to make a simple song player. For this, I am using winmm.dll (did not find an easy cross-platform solution). The problem is this: I need to update a trackbar along with the song playing. I have two functions, Player.GetLength and Player.GetCurrentPosition, which return the time in miliseconds. If I call them "normally", everything is ok. But I need to call them in a timer, like this:
new System.Threading.Timer((state) =>
{
length = Player.GetLength();
pos = Player.GetCurrentPosition();
trackBar1.Value = (pos / length) * 100;
}, null, 0, 100);
This is GetLength, and GetCurrentPosition is similar:
public static int GetLength()
{
StringBuilder s = new StringBuilder(128);
mciSendString("status Song length", s, s.Capacity, IntPtr.Zero);
return int.Parse(s.ToString());
}
The problem: when one of these two functions gets called, the program just stops, without any warning or exception thrown. Note: I am using .NET
So I was wondering if you can explain to me where I got it wrong :)

One thing I'd note is that System.Threading.Timer fires it's callback in it's own thread. Since you are interacting with the UI, you'd either want to use System.Windows.Forms.Timer (as a component on the form) or invoke back to the UI, as follows:
new System.Threading.Timer((state) =>
{
length = Player.GetLength();
pos = Player.GetCurrentPosition();
trackBar1.Invoke(new Action(()=>trackBar1.Value = (pos / length) * 100));
}, null, 0, 100);
Likewise, I am not sure if the Player class supports/tolerates multiple threads, but if not, there is the possibility that the whole callback needs to be invoked to the UI.

Related

Storyboard animation is not in sync with the previous animation

I am trying to implement MiddleClickScrolling in ScrollViewer and it works well.
The problem is when moving the pointer the Storyboard will restart to update the speed but when we move the pointer a jitter is occurring. I have attached a gif but you may not notice this jitter in this gif.
Since this a big class, I can't put all the code here. You can see my full code on GitHub (Note: Please select SmoothScroll branch if you are cloning it). An easy way to reproduce this issue is to move the pointer Up and Down for a small distance rapidly.
This is my code for storyboard animation
_verticalDoubleAnimation = new DoubleAnimation()
{
EnableDependentAnimation = true,
Duration = new TimeSpan(0, 0, 1)
};
//Different function
var offsetX = _currentPosition.X - _startPosition.X;
var offsetY = _currentPosition.Y - _startPosition.Y;
SetCursorType(offsetX, offsetY);
if (CanScrollVertical())
{
if (Math.Abs(offsetY) > _threshold)
{
RunInUIThread(() =>
{
_verticalDoubleAnimation.From = _scrollViewer.VerticalOffset;
_verticalDoubleAnimation.To = _scrollViewer.VerticalOffset + (offsetY > 0 ? _scrollViewer.ScrollableHeight : -_scrollViewer.ScrollableHeight);
if ((_scrollViewer.ScrollableHeight / (Math.Abs(offsetY) * _factor)) == double.NaN)
{
return;
}
_verticalDoubleAnimation.Duration = TimeSpan.FromSeconds(_scrollViewer.ScrollableHeight / (Math.Abs(offsetY) * _factor));
_verticalStoryboard.Begin();
});
}
else
{
RunInUIThread(() =>
{
_verticalStoryboard.Stop();
_sliderVertical.Value = _scrollViewer.VerticalOffset;
});
}
}
Instead of animating it, use a timer or a while loop to dynamically update the position using the ScrollViewer.ChangeView() method. Make sure the interval is less than 16.6ms for smooth 60fps motion. Also make sure to set the final parameter of ChangeView to false so that its built-in animation is disabled.
So in your first commit, change this:
timer = new Timer(ScrollAsync, null, 50, 50);
To this:
timer = new Timer(ScrollAsync, null, 0, 5);
The Timer does not give precision within 1ms or even 10ms to my undestanding. Overcompensating by updating it every 1-5ms should make up for that so it's probably fine and it's what I'm using in my custom scrolling control. A Stopwatch is very precise but is a massive CPU hog. Another option: It's a lot of extra work, but if you want precision timing and maximum performance with minimal battery usage, you can use Win2D's CanvasAnimatedControl which can be used to run code exactly once every 60th of a second.

Coroutine only firing once

I'm trying to run a script that will generate a "ramps" upon touch between point a and b. This code receives a list of where the elements of the ramps should be and then instanciates and places them on the screen.
However the coroutine is only running once and I can't understand why. Can anyone give me some advice?
Thank you very much in advance
public IEnumerator CreateRamp(List<Vector3> lP, float angle)
{
int i = 1;
while (i <= lP.Count)
{
Debug.Log("Iteration " + i + " of " + lP.Count + " position is " + lP[i]);
GameObject item = Instantiate(Resources.Load("floor")) as GameObject;
item.transform.position = current_Position;
item.transform.eulerAngles = new Vector3(0, 0, UnityEngine.Random.Range(0f, 360f));
item.GetComponent<Ramp>().strtPos = item.transform.position;
item.GetComponent<Ramp>().strtRot = item.transform.eulerAngles;
item.GetComponent<Ramp>().strtScale = new Vector3(0.4f, 0.4f, 1);
item.GetComponent<Ramp>().tgtRot = new Vector3(0, 0, angle);
item.GetComponent<Ramp>().tgtPos = lP[i-1];
i += 1;
yield return new WaitForSeconds(0.2f);
}
}
I suspect your condition i <= lP.Count is true only once. (Maybe lP.Count == 1, I think).
The way co-routine works is that, the code inside the CreateRamp function is executed across multiple frames.
When you StartCoroutine(CreateRamp(...)), it is immediately run until it hits yield statement. It will wait there for 0.2 seconds and will be run again from the statement right after the yield.
In the second execution, it evaluates the condition i <= lP.Count again and see that it is False => it jumps out of the loop and because it hits the end of the function, that co-routine will be stopped, no more execution in the future.
Since this function is an IEnumerable it should be treated by other code as a list of Ramp objects. I suspect (there isn't enough of your code to know), that the way you are calling this function is incorrect.
On a side note, with your yield returning a waitforX, It would be better in the long term to either perform the wait outside of this function (where you are calling it from) or at the very least add the wait period as a parameter to the function. Hard coded values like that will end up biting you later on, especially if your game's code-base grows. I recommend it be exposed as a setting on your GameObject, so it can be tweaked from the editor.
One other thing, How do you destroy these Ramp objects when you are done with them? It might be good to consider storing references to them as you create them so you can destroy them later.

How to use two objects from a pooled list in one script?

I am trying to understand object pooling. I am able to get the script to pull one object at a time, but I need to be able to pull three or more from the list at the same time.
My object pooling script is big, so I don't really want to share the whole thing unless it is necessary.
I need to be able to change the location of the spawn of a flame, so I created a script to do that:
private void CreateWavesForFlames(GameObject flame, float xBase, float xDisplacement, float dropHeight)
{
flame.transform.position = new Vector3(xBase + xDisplacement, dropHeight, 0);
flame.SetActive(true); //this turn the pooled object on
}
So I need to spawn three flames at the same time and change their spawn locations
The wave call would look something like this:
void Wave1() {
Debug.Log("Wave1");
tempGOHolder = gm.GetLargeFire();
CreateWavesForFlames(tempGOHolder, 0, 0, 12);
CreateWavesForFlames(tempGOHolder, 10, 0, 12);
CreateWavesForFlames(tempGOHolder, 15, 0, 12);
}
What happens is only one fire flame is created and it uses the last CreatWavesForFlames. I need the three to be different.
Any suggestions on how to do this would be awesome.
I know this has been answered but I think there is a better solution. The answer above is great. Assuming you want to make your code shorter by not repeatedly calling the GetLargeFire() function, you can use the method below.
Lets say that your GetLargeFire() function in your pool script looks like this:
GameObject GetLargeFire()
{
return availableGameObject;
}
You can create an overload of the GetLargeFire() function that fills up an array that is passed to it. I wouldn't recommend returning array because that allocates memory, therefore making your pooling script useless. Filling up array that is passed in is better.
public void GetLargeFire(GameObject[] gOBJ, int amountToReturn)
{
for (int i = 0; i < gOBJ.Length; i++)
{
if (i < amountToReturn)
{
gOBJ[i] = GetLargeFire();
}
else
{
//Fill the rest with null to override what was inside of it previously
gOBJ[i] = null;
}
}
}
Now, to use it like the example you have in your question, you should declare tempGOHolder as an array and choose a number you you think is enough for it. We will use 3 for this example.
GameObject[] tempGOHolder;
void Start()
{
tempGOHolder = new GameObject[3];
}
void Wave1() {
Debug.Log("Wave1");
gm.GetLargeFire(tempGOHolder, 3);
CreateWavesForFlames(tempGOHolder[0], 0, 0, 12);
CreateWavesForFlames(tempGOHolder[1], 10, 0, 12);
CreateWavesForFlames(tempGOHolder[2], 15, 0, 12);
}
You can even use loop to create Waves with CreateWavesForFlames with less code.
Well.. that is what is expected from your code. if you want 3 different flame objects, Then you are going to have to do this(assuming "gm" is your pool manager object):
tempGOHolder = gm.GetLargeFire();
CreateWavesForFlames(tempGOHolder, 0, 0, 12);
tempGOHolder = gm.GetLargeFire();
CreateWavesForFlames(tempGOHolder, 10, 0, 12);
tempGOHolder = gm.GetLargeFire();
CreateWavesForFlames(tempGOHolder, 15, 0, 12);

Asynchronous method call in C#

We have such a situation. We have a canvas, on which some ammount of figures are rendered. It may be 1 or many more (for example thousand) and we need to animate their translation to another location (on button click) using storyboard:
internal void someStoryBoard(figure someFigure, double coordMoveToValue)
{
string sbName = "StoryBoard_" + figure.ID;
string regName = "figure_" + figure.ID;
try
{
cnvsGame.Resources.Remove(sbName);
cnvsGame.UnregisterName(regName);
}
catch{ }
someCanvas.RegisterName(regName, someFigure.Geometry);
var moveFigureYAnimation = new PointAnimation();
moveFigureYAnimation.From = new Point(someFigure.Geometry.Center.X, someFigure.Geometry.Center.Y);
moveFigureYAnimation.To = new Point(someFigure.eGeometry.Center.X, coordMoveToValue);
moveFigureYAnimation.Duration = TimeSpan.FromSeconds(0.5);
var sbFigureMove = new Storyboard();
Storyboard.SetTargetName(sbFigureMove, regName);
Storyboard.SetTargetProperty(sbFigureMove, new PropertyPath(Geometry.CenterProperty));
sbFigureMove.Children.Add(moveFigureYAnimation);
cnvsGame.Resources.Add(sbName, sbFigureMove);
sbFigureMove.Begin();
}
Figures are stored in list. We are calling this StoryBoard using for loop:
for(int i = 0; i<listOfFigures.Count; i++)
{
someStoryBoard(listOfFigures[i], someCoord);
}
But here's the problem: if we have a little amount of figures - code completes quickly. But if ammount is big - there is a delay after a button is clicked and before the figures begin to move.
So, here's the question: is it possible to call someStoryBoard method asynchronously? Is next algorithm possible -> When someStoryBoard is called it begins to move figure instantly, not waiting for whole for loop to complete.?
You can add actions into Dispatcher queue by calling Dispatcher.InvokeAsync. You can also specify dispatcher priority, depending on your requirements.
Please note that moving thousands of items can't be reliably fast, so you may need to rethink the drawing logic. If even starting animation is slow, it's highly likely animating won't be fast enough too.
You can try use async/await modifier
async internal Task someStoryBoard(figure someFigure, double coordMoveToValue)

Best way to move an image across part of the screen?

I have a Silverlight WP7 app and an image on my page that I want to appear to slide across the screen. What is the best way of doing this? I wrote this real quick but the UI doesn't update until the entire method is done.
private void SpinImg(Image img, double left) {
for(int i = 1; i <= 10000; i++) {
img.Margin = new Thickness(left, img.Margin.Top + 1, 0, 0);
if(img.Margin.Top > 314) {
//move it to the top
img.Margin = new Thickness(left, -105, 0, 0);
}
int wait = 1000 / i;
Thread.Sleep(wait);
}
}
Use a Storyboard - this is hardware-acceleratable, and all occurs on the Render thread, so you'll see much better performance than trying to update position directly over and over.
Storyboard has the advantage of being time-based instead of frame-based, so it's easy to declare "I want the image to move from to in 0.5 seconds" and it will just happen.
Thread.Sleep will freeze ALL UI processing, use Dispatcher class.

Categories