I'm using a progress bar to show the user how far along the process is. It has 17 steps, and it can take anywhere from ~5 seconds to two or three minutes depending on the weather (well, database)
I had no problem with this in XP, the progress bar went fine, but when testing it in vista I found that it is no longer the case.
For example: if it takes closer to 5 seconds, it might make it a 1/3 of the way through before disappearing because it's completed. Even though it's progress is at 17 of 17, it doesn't show it. I believe this is because of the animation Vista imposes on progress bars and the animation cannot finish fast enough.
Does anyone know how I can correct this?
Here is the code:
This is the part that updates the progress bar, waiting is the form that has the progress bar.
int progress = 1;
//1 Cash Receipt Items
waiting.setProgress(progress, 18, progress, "Cash Receipt Items");
tblCashReceiptsApplyToTableAdapter1.Fill(rentalEaseDataSet1.tblCashReceiptsApplyTo);
progress++;
//2 Cash Receipts
waiting.setProgress(progress, "Cash Receipts");
tblCashReceiptsTableAdapter1.Fill(rentalEaseDataSet1.tblCashReceipts);
progress++;
//3 Checkbook Codes
waiting.setProgress(progress, "Checkbook Codes");
tblCheckbookCodeTableAdapter1.Fill(rentalEaseDataSet1.tblCheckbookCode);
progress++;
//4 Checkbook Entries
waiting.setProgress(progress, "Checkbook Entries");
tblCheckbookEntryTableAdapter1.Fill(rentalEaseDataSet1.tblCheckbookEntry);
progress++;
//5 Checkbooks
waiting.setProgress(progress, "Checkbooks");
tblCheckbookTableAdapter1.Fill(rentalEaseDataSet1.tblCheckbook);
progress++;
//6 Companies
waiting.setProgress(progress, "Companies");
tblCompanyTableAdapter1.Fill(rentalEaseDataSet1.tblCompany);
progress++;
//7 Expenses
waiting.setProgress(progress, "Expenses");
tblExpenseTableAdapter1.Fill(rentalEaseDataSet1.tblExpense);
progress++;
//8 Incomes
waiting.setProgress(progress, "Incomes");
tblIncomeTableAdapter1.Fill(rentalEaseDataSet1.tblIncome);
progress++;
//9 Properties
waiting.setProgress(progress, "Properties");
tblPropertyTableAdapter1.Fill(rentalEaseDataSet1.tblProperty);
progress++;
//10 Rental Units
waiting.setProgress(progress, "Rental Units");
tblRentalUnitTableAdapter1.Fill(rentalEaseDataSet1.tblRentalUnit);
progress++;
//11 Tenant Status Values
waiting.setProgress(progress, "Tenant Status Values");
tblTenantStatusTableAdapter1.Fill(rentalEaseDataSet1.tblTenantStatus);
progress++;
//12 Tenants
waiting.setProgress(progress, "Tenants");
tblTenantTableAdapter1.Fill(rentalEaseDataSet1.tblTenant);
progress++;
//13 Tenant Transaction Codes
waiting.setProgress(progress, "Tenant Transaction Codes");
tblTenantTransCodeTableAdapter1.Fill(rentalEaseDataSet1.tblTenantTransCode);
progress++;
//14 Transactions
waiting.setProgress(progress, "Transactions");
tblTransactionTableAdapter1.Fill(rentalEaseDataSet1.tblTransaction);
progress++;
//15 Vendors
waiting.setProgress(progress, "Vendors");
tblVendorTableAdapter1.Fill(rentalEaseDataSet1.tblVendor);
progress++;
//16 Work Order Categories
waiting.setProgress(progress, "Work Order Categories");
tblWorkOrderCategoryTableAdapter1.Fill(rentalEaseDataSet1.tblWorkOrderCategory);
progress++;
//17 Work Orders
waiting.setProgress(progress, "Work Orders");
tblWorkOrderTableAdapter1.Fill(rentalEaseDataSet1.tblWorkOrder);
progress++;
//18 Stored procs
waiting.setProgress(progress, "Stored Procedures");
getAllCheckbookBalancesTableAdapter1.Fill(rentalEaseDataSet1.GetAllCheckbookBalances);
getAllTenantBalancesTableAdapter1.Fill(rentalEaseDataSet1.GetAllTenantBalances);
//getCheckbookBalanceTableAdapter1;
//getTenantBalanceTableAdapter1;
getTenantStatusID_CurrentTableAdapter1.Fill(rentalEaseDataSet1.GetTenantStatusID_Current);
getTenantStatusID_FutureTableAdapter1.Fill(rentalEaseDataSet1.GetTenantStatusID_Future);
getTenantStatusID_PastTableAdapter1.Fill(rentalEaseDataSet1.GetTenantStatusID_Past);
selectVacantRentalUnitsByIDTableAdapter1.Fill(rentalEaseDataSet1.SelectVacantRentalUnitsByID);
getRentBasedBalancesTableAdapter1.Fill(rentalEaseDataSet1.GetRentBasedBalances);
getAgingBalanceTableAdapter2.Fill(rentalEaseDataSet1.GetAgingBalance);
waiting.Close();
Here is the waiting form:
public partial class PleaseWaitDialog : Form {
public PleaseWaitDialog() {
CheckForIllegalCrossThreadCalls = false;
InitializeComponent();
}
public void setProgress(int current, int max, int min, string loadItem) {
Debug.Assert(min <= max, "Minimum is bigger than the maximum!");
Debug.Assert(current >= min, "The current progress is less than the minimum progress!");
Debug.Assert(current <= max, "The progress is greater than the maximum progress!");
prgLoad.Minimum = min;
prgLoad.Maximum = max;
prgLoad.Value = current;
lblLoadItem.Text = loadItem;
}
public void setProgress(int current, string loadItem) {
this.setProgress(current, prgLoad.Maximum, prgLoad.Minimum, loadItem);
}
}
Vista introduced an animation effect when updating the progress bar - it tries to smoothly scroll from the previous position to the newly-set position, which creates a nasty time lag in the update of the control. The lag is most noticeable when you jump a progress bar in large increments, say from 25% to 50% in one jump.
As another poster pointed out, you can disable the Vista theme for the progress bar and it will then mimic the behavior of XP progress bars.
I have found another workaround: if you set the progress bar backwards, it will immediately paint to this location. So, if you want to jump from 25% to 50%, you would use the (admittedly hackish) logic:
progressbar.Value = 50;
progressbar.Value = 49;
progressbar.Value = 50;
I know, I know - it's a silly hack - but it does work!
The reason for this whole mess is the interpolating animation effect introduced by Vista and W7. It has aboslutely nothing to do with thread blocking issues. Calling setProgress() or setting the Value property driectly, triggers an animation effect to occur, which I will explain how to cheat:
I came up with a hack of setting the maximum according to a fixed value. The maximum property does not trigger the effect, thus you get to freely move the progress around with instant response.
Remember that the actual shown progress is given by: ProgressBar.Value / ProgressBar.Maximum. With this in mind, the example below will move the progress from 0 to 100, repensented by i:
ProgressBar works like this:
progress = value / maximum
therefore:
maximum = value / progress
I added some scaling factors needed, should be self explanatory:
progressBar1.Maximum *= 100;
progressBar1.Value = progressBar1.Maximum / 100;
for (int i = 1; i < 100; i++)
{
progressBar1.Maximum = (int)((double)progressBar1.Value / (double)(i + 1) * 100);
Thread.Sleep(20);
}
It sounds like you're doing everything on the UI thread and thus not releasing the message pump. Have you tried using smoething like BackgroundWorker and the ProgressChanged event? See MSDN for an example.
BackgroundWorker is ideal for loading external data - but note that you shouldn't do any data-binding etc until you get back to the UI thread (or just use Invoke/BeginInvoke to push work to the UI thread).
Try invoking the call to the waiting.setProgess() method since waiting seems to live in another thread and this would be a classic cross thread call (which the compiler warns you about if you let him).
Since Control.Invoke is a bit clumsy to use I usually use an extension method that allows me to pass a lambda expression:
waiting.ThreadSafeInvoke(() => waiting.setProgress(...));
.
// also see http://stackoverflow.com/questions/788828/invoke-from-different-thread
public static class ControlExtension
{
public static void ThreadSafeInvoke(this Control control, MethodInvoker method)
{
if (control != null)
{
if (control.InvokeRequired)
{
control.Invoke(method);
}
else
{
method.Invoke();
}
}
}
}
I use Mark Lansdown's excellent answer as an Extension method for the ProgressBar control.
public static void ValueFast(this ProgressBar progressBar, int value)
{
progressBar.Value = value;
if (value > 0) // prevent ArgumentException error on value = 0
{
progressBar.Value = value - 1;
progressBar.Value = value;
}
}
Or you can also do it this way which only sets the ProgressBar value property twice instead of three times:
public static void ValueFast(this ProgressBar progressBar, int value)
{
if (value < 100) // prevent ArgumentException error on value = 100
{
progressBar.Value = value + 1; // set the value +1
}
progressBar.Value = value; // set the actual value
}
Simply call it on any ProgressBar control using the extension method:
this.progressBar.ValueFast(50);
If you really wanted to you could also check the current Windows Environment and only do the hack section of code for Windows Vista+ since Windows XP's ProgressBar does not have the slow progress animation.
Expanding on the answer given by Silas Hansen, this one seems to give me perfect results every time.
protected void UpdateProgressBar(ProgressBar prb, Int64 value, Int64 max)
{
if (max < 1)
max = 1;
if (value > max)
value = max;
Int32 finalmax = 1;
Int32 finalvalue = 0;
if (value > 0)
{
if (max > 0x8000)
{
// to avoid overflow when max*max exceeds Int32.MaxValue.
// 0x8000 is a safe value a bit below the actual square root of Int32.MaxValue
Int64 progressDivideValue = 1;
while ((max / progressDivideValue) > 0x8000)
progressDivideValue *= 0x10;
finalmax = (Int32)(max / progressDivideValue);
finalvalue = (Int32)(value / progressDivideValue);
}
else
{
// Upscale values to increase precision, since this is all integer division
// Again, this can never exceed 0x8000.
Int64 progressMultiplyValue = 1;
while ((max * progressMultiplyValue) < 0x800)
progressMultiplyValue *= 0x10;
finalmax = (Int32)(max * progressMultiplyValue);
finalvalue = (Int32)(value * progressMultiplyValue);
}
}
if (finalvalue <= 0)
{
prb.Maximum = (Int32)Math.Min(Int32.MaxValue, max);
prb.Value = 0;
}
else
{
// hacky mess, but it works...
// Will pretty much empty the bar for a split second, but this is normally never visible.
prb.Maximum = finalmax * finalmax;
// Makes sure the value will DEcrease in the last operation, to ensure the animation is skipped.
prb.Value = Math.Min(prb.Maximum, (finalmax + 1));
// Sets the final values.
prb.Maximum = (finalmax * finalmax) / finalvalue;
prb.Value = finalmax;
}
}
First. I'd never turn off the CheckForIllegalCrossThreadCalls option.
Second. Add a Refresh() after you update the progress. Just because you're doing work in a different thread doesn't mean your GUI thread is going to get around to updating.
I have the same problem.
I have a form with multiple progress bars (top one is for example file x/n, bottom one is task y/m)
The top progress bar does not update TIMELY while the bottom one does
Programatically I update it, invalidate, explicit process message, refresh or sleep does not fix it. Funny thing is that bottom progress bar and other component (time elapsed text) updates fine.
This is purely a Vista+theme problem (animations like previously suggested, XP or Vista with classic theme works fine.
When displaying a message box after the top progress bar has travelled to 100 (programmatically, not visually) I first see the message box and then I see the progress completing
I found that SetWindowTheme(ProgressBar.Handle, ' ', ' '); as explained on
Disabling progress bar animation on Vista Aero
works (but I have old style progress bars now)
Did you try Application.DoEvents(); ?
Related
I made a loading bar following Brackeys' tutorial here (and have changed a few things later). I noticed that when I load a level, and my loading bar comes out, it first shows 0%, waits, then directly 100% - without intermediate values!.
I'm guessing it could be due to some lag that occurs because the game is already using a lot of hardware, and hasn't left enough power for the rest of the processes, because I tried making it load while doing other things at the same time.
Here is the code that loads the level and changes the slider's value:
public void LoadLevel(int sceneIndex) {
StartCoroutine(StartFadeThenLoadLevel(sceneIndex));
}
IEnumerator StartFadeThenLoadLevel(int sceneIndex)
{
yield return new WaitForSeconds(0.9f);
StartCoroutine(LoadAsynchronously(sceneIndex));
}
IEnumerator LoadAsynchronously(int sceneIndex) {
yield return null;
AsyncOperation operation = SceneManager.LoadSceneAsync(sceneIndex);
loadingScreen.SetActive(true);
chooseLevel.SetActive(false);
while (!operation.isDone) {
float progress = Mathf.Clamp01(operation.progress / 0.12f);
if (progress > 1) progress = 1;
slider.value = progress;
progressText.text = (int)(progress * 100f) + "%";
yield return null;
}
}
I saw that this mostly happens on more powerful phones, when it doesn't take more than a second to load the scene, than in older (slower) ones.
How can I fix this? I would like to have a very smooth loading bar that updates at least every 5%!
Thanks in advance for any help!
Edit: I added an FPS counter (Graphy) and i saw that there was a huge frame drop down to 1fps. That is probably what causes everything! Is there any way to prevent that?
(If you think this is likely to be the problem, please answer this question instead of the main one!)
int progress is 0 when operation.progress <= 0 and it is 1 when operation.progress > 0.
So your progress is always 1 because operation.progress is always positive.
Try to simplify your progress calculation:
IEnumerator load(){
yield return null;
//Begin to load the Scene you specify
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneIndex);
//Don't let the Scene activate until you allow it to
asyncOperation.allowSceneActivation = false;
Debug.Log("Pro :" + asyncOperation.progress);
//When the load is still in progress, output the Text and progress bar
while (!asyncOperation.isDone)
{
//Output the current progress
Debug.Log("Loading progress: " + (asyncOperation.progress * 100) + "%");
// Check if the load has finished
if (asyncOperation.progress >= 0.9f)
{
//Change the Text to show the Scene is ready
asyncOperation.allowSceneActivation = true;
}
yield return null;
}
}
If it's super important to show this loading bar, you may need to implement it by showing a platform native UI widget.
so im attempting to make a simple function to fade in and out a UIElement
the issue that is occuring is the element fades in fine and has an opacity of 1 but on the fade out the opacity finishes at arround 0.01 oppose to 0.
public static async Task<bool> FadeIO(UIElement target, int FadeTime = 100, int DelayBeforeFade = 5000, int DelayBeforeOutFade = 10000)
{
double OpacTick = 0; //The "counter" for the while loops.
double FadeAmount = ((double)1 / FadeTime); //Calculates the required opacity increment for the fade to happen in the specified time.
await Task.Delay(DelayBeforeFade); //Holds until the required delay before the fade has been reached.
do
{
target.Opacity = 0 + OpacTick; //Alters the target's opacity based on the current loop cylce.
OpacTick += FadeAmount; //Alters the counter by the pre calculated alteration amount.
await Task.Delay(1); //Halts the loop
} while (OpacTick <= 1); //Loops finished when the target's opacity is 1.
OpacTick = 0; //Resets the loop counter.
await Task.Delay(DelayBeforeOutFade); //Holds until the required delay before the fade out has been reached
do
{
target.Opacity = 1 - OpacTick; //Alters the target's opacity based on the current loop cylce.
OpacTick += FadeAmount; //Alters the counter by the pre calculated alteration amount.
await Task.Delay(1); //Halts the loop
} while (OpacTick <= 1); //Loops finished when the target's opacity is 0 and the counter is therefor 1.
return true;
}
The strangest thing about the issue is that when tested on different systems the opacity can fully return to 0 while on some systems it end at arround 0.01
That last loop iteration doesn't get you to 0:
You set the opacity to ~0.01, then increment OpacTick, which is then checked against 1. It never actually sets the opacity to the incremented amount.
That being said, this is totally unnecessary. A DoubleAnimationUsingKeyFrames would serve you far better.
I want to be able to flash stuff at a certain frequency. For an example, let's say 2Hz. I also want to be able to specify a ratio, where I can have the thing displayed for let's say 2/3 of the cycle and have it hidden for 1/3, so the ratio would be 2:1. It's a wild bunch of flashing, so I Need to stay flexible in the way I do it. There might be some flashing with a ratio of 3:5 and a frequency of 2Hz, and some other flashing at 4Hz with ratio 1:1, and so on.
Also, I need to be able to flash in sync. So if one object is flashing already and I start flashing another one, they need to be in sync (or rather their cycles need to be in sync, the flashing may vary as the ratio may be different). But if at the same frequency, they need to "turn on" at the same time, even if their ratios are different. Also, they all need to turn on at the same time the slowest turns on.
My current approach: I have a GameObject FlashCycle, that essentially in it's update method calculates a progress for the 3 frequency's I have (2Hz, 4Hz and 8Hz).
float time = Time.time;
twoHerzProgress = (time % twoHerzInSeconds) / twoHerzInSeconds;
fourHerzProgress = (time % fourHerzInSeconds) / fourHerzInSeconds;
eightHerzProgress = (time % eightHerzInSeconds) / eightHerzInSeconds;
I have tried different times, but that didn't really matter so let's just stick to that one if you don't think it's a bad idea!
Now, whenever I want to flash an object, in it's own Update() I do this:
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
show = flashCycle.oneHerzProgress <= onTimePercentage;
case FlashRateInterval.fourHerz:
show =flashCycle.twoHerzProgress <= onTimePercentage;
case FlashRateInterval.eightHerz:
show =flashCycle.fourHerzProgress <= onTimePercentage;
default:
show =true;
}
and then just continue and have the object displayed if show == true.
Unfortunately this doesn't flash the objects at a nice smooth and regular interval. I measured the 2Hz interval and got differences in the ratio of up to 48ms, and though it seems like not much it really makes a difference on the screen.
So the question boils down to: How can I get quick, reqular flashes while maintaining the flexibility (ratio and frequency wise) and have a syncronized flash?
Thanks for your help!
You could use Coroutines and WaitForSeconds to achieve that
// onRatio and offRatio are "optional" parameters
// If not provided, they will simply have their default value 1
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
while(true)
{
show = true;
yield return new WatForSeconds(onDuration);
show = false;
yield return new WatForSeconds(offDuration);
}
}
so you can call it either with a frequency e.g. 8Hz
StartCoroutine(Flash(8.0f));
this is actually equal to any call where you set onRatio = offRatio e.g.
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 1));
StartCoroutine(Flash(8.0f, onRatio = 2, offRatio = 2));
....
or with a frequency and ratios e.g. 1(on):2(off) with 8Hz
StartCoroutine(Flash(8.0f, onRatio = 1, offRatio = 2));
With this setup the Coroutine runs "forever" in the while(true)-loop. So, don't forget before you start a new Coroutine with different parameters to first stop all routines with
StopAllCoroutines();
Now if you want to change that dynamically in an Update method, you would have to add some controll flags and additional variables in roder to make sure a new Coroutine is only called when something changed:
FlashRateInterval currentInterval;
float currentOnRatio = -1;
float currentOffRatio = -1;
void Update()
{
// if nothing changed do nothing
if(flashRate.herz == currentInterval
//todo && Mathf.Approximately(<yourOnRatio>, currentOnRatio)
//todo && Mathf.Approximately(<yourOffRatio>, currentOffRatio)
) return;
StopAllCoroutines();
currentInterval = flashRate.herz;
//todo currentOnRatio = <yourOnRatio>;
//todo currentOffRatio = <yourOffRatio>;
switch (flashRate.herz)
{
case FlashRateInterval.twoHerz:
StartCoroutine(2.0f);
//todo StartCoroutine(2.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.fourHerz:
StartCoroutine(4.0f);
//todo StartCoroutine(4.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
case FlashRateInterval.eightHerz:
StartCoroutine(8.0f);
//todo StartCoroutine(8.0f, onRatio = <yournRatio>, offRatio = <yourOffRatio>);
default:
show =true;
}
}
Notes:
I dont know your FlashRateInterval but if you need to use it for some reason you could make it like
public enum FlashRateInterval
{
AllwaysOn,
twoHerz = 2,
fourHerz = 4,
eightHerz = 8
}
in order to directly use the correct values.
I would call a frequency variable flashRate.herz. You also wouldn't call a size value cube.meters. I'ld recommend to rename it to flashRate.frequency.
To archieve that syncing you would somehow need access to all Behaviours and compare their values (so I'ld say some static List<YourBehavior>) and than e.g. in the Coroutine wait until all bools are e.g. set to true before continuing with your own one. For that you would need an additional bool since it is possible that show is true permanently on one component.
public bool isBlinking;
IEnumerator Flash(float frequency ,float onRatio = 1, float offRatio = 1)
{
//todo: You'll have to set this false when not blinking -> in Update
isBlinking = true;
float cycleDuration = 1.0f / frequency;
float onDuration = (onRatio/ (onRatio + offRatio)) * cycleDuration;
float offDuration = (offRatio/ (onRatio + offRatio)) * cycleDuration;
// SYNC AT START
show = false;
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// => this line is reached when all show are false
// Now lets just do the same but this time wating for true
// wait until all show get false
foreach(var component in FindObjectsOfType<YOUR_COMPONENT>())
{
// skip checking this component
if(component == this) continue;
// if the component is not running a coroutine skip
if(!component.isBlinking) continue;
// Now wait until show gets false
while(!component.show)
{
// WaitUntilEndOfFrame makes it possible
// for us to check the value again already before
// the next frame
yield return new WaitForEndOfFrame;
}
}
// this line is reached when all show are getting true again => begin of loop
while(true)
{
.........
Instead of using FindObjectsOfType<YOUR_COMPONENT>() which is kind of slow you could also do something like
public static List<YOUR_COMPONENT> Components = new List<YOUR_COMPONENT>();
private void Awake()
{
if(!Components.Contains(this)){
Components.Add(this);
}
}
so you also get currently disabled components and objects
You got some diferences because you are doing everything in an Update() cycle with <= condition. On slower/faster machines you will have more/less differences because the frame's duration will never be equal to your frequency.
Try doing everything in a Corotine: unity coroutine docs
//bad code below but i think its more understandable like this
IEnumerator Flash()
{
while(true)
{
BlinkOn();
Sync();//sync here another cicle if you want to sync when on starts
yield return new WaitForSeconds(yourDuration);// yourDuration*multiplier/something+0.5f....ecc
BlinkOff()
Sync();//sync here another cicle if you want to sync when of starts
yield return new WaitForSeconds(yourDuration);
}
}
I am using SharpDX to make a GUI interface and associated code to allow control of the mouse cursor/emulate key presses but even at idle my app uses 15%+ cpu to the point where it increases my CPU temp +10C while my app is running, here is controller update loop running in its own thread:
private void CallToChildThread()
{
Program.stateOld = controller.GetState();
while (controller.IsConnected)
{
Program.stateNew = controller.GetState();
CheckButtons();
Point cursor;
GetCursorPos(out cursor);
short tx;
short ty;
tx = Program.stateNew.Gamepad.LeftThumbX;
ty = Program.stateNew.Gamepad.LeftThumbY;
float x = cursor.X + _xRest;
float y = cursor.Y + _yRest;
float dx = 0;
float dy = 0;
// Handle dead zone
float lengthsq = tx * tx + ty * ty;
if (lengthsq > DEAD_ZONE * DEAD_ZONE)
{
float mult = speed * getMult(lengthsq, DEAD_ZONE, acceleration_factor);
dx = getDelta(tx) * mult;
dy = getDelta(ty) * mult;
}
x += dx;
_xRest = x - (int)x;
y -= dy;
_yRest = y - (int)y;
SetCursorPos((int)x, (int)y);
Program.stateOld = Program.stateNew;
}
}
CheckButtons is just a function i use to check which buttons have been pressed, I've tried running this inside the controller update thread but I cannot get the buttons to perform as expected.
the while() and GetCursorPos contribute the most towards the total cpu usage. I've tried to compare packet numbers so my app is idle when the controller isn't being used but that causes major issues with handling cursor position(it's very slow and intermittent)
Edit: I've set the thread to background and its priority to BelowNormal but that didn't improve CPU usage very much
This is a tight loop, so it's going to fully use a CPU core. Just add a Thread.Sleep(10) at the end of each iteration. Adjust the delay if it's not responsive enough, but with 10 ms it'll be polling the controller state 100 times per second, which is already a lot.
private void CallToChildThread()
{
Program.stateOld = controller.GetState();
while (controller.IsConnected)
{
// Your update logic
Thread.Sleep(10); // Adjust the delay as needed
}
}
Also, note that lowering the thread priority is not going to decrease CPU usage. It just tells your thread to yield to another if there's not enough CPU available for everybody, which obviously isn't the case.
Update:
I isolated the problem to its core components and a much shorter minimal working example in another question:
Observable.Interval not updating UI with expected frequency
I need to display a sequence of images (which are read from a folder) as a "movie", at a constant, previously known, frame-rate.
I have an Image control in WPF, whose Source is data-bound to a ViewModel property Image. I then proceed to update that image on a timely basis, using an Observable.Interval event source. For each elapsed interval, the UpdateImage is called, which updates the image inside a Task.Run() call.
My problem is: when I experimentally increase effective frame-rate (which depends on actual frame-rate and also on playback speed), the playback keeps being at normal speed up tu a given value. Above that value, it starts to look slower.
I believe that it has to do with RaisePropertyChanged call, but I'm not sure. I tried using SubscribeOnDispatcher (or whas it ObserveOnDispatcher?) but anyway it didnt have an effect.
Questions are:
- What I am doing wrong?
- How could I investigate and resolve the problem?
UPDATE:
It's worth mentioning that the getter for Image calls CreateImageForIndex(), which is a method that could have a non-trivial cost. Would it be possible to "async" it?
ViewModel (partial):
CancellationTokenSource _cancelPlay;
double _speed;
public void Play(double speed)
{
_speed = speed;
_cancelPlay = new CancellationTokenSource();
Observable.Interval(TimeSpan.FromSeconds(1.0 / (Math.Abs(speed) * Exame.Model.Configurações.FrameRate)))
.Subscribe(t => ExecuteRun(), _cancelPlay.Token);
}
public void Stop()
{
_cancelPlay?.Cancel();
}
void ExecuteRun()
{
Task.Run(() =>
{
Index = Math.Max(0, Math.Min(_model.MaxIndex, Index + 1 * Math.Sign(_speed)));
});
}
public int Index
{
get { return _model.Index; }
set
{
_model.Index = value;
RaisePropertyChanged(null); // this tells view to get a new value for `Image` too!
}
}
public ImageSource Image => _model.CreateImageForIndex(Index);
I've followed a hint from #Enigmativity (in the other answer I posted), that in Windows the resolution of timers is 15ms at most.
So I tested another strategy: spinning a while loop with an "when elapsed" condition, similar to a game loop, measuring time with a Stopwatch, and everything is working fine now.
public void Play(double speed)
{
_speed = speed;
_cancelPlay?.Cancel();
_cancelPlay = new CancellationTokenSource();
Task.Run(() => PlayLoop(_cancelPlay.Token), _cancelPlay.Token);
}
void PlayLoop(CancellationToken token)
{
var sw = Stopwatch.StartNew();
double previous = sw.ElapsedMilliseconds;
double timeInterval = 1000.0 / (Math.Abs(_speed) * _exame.Model.Configurações.FrameRate);
while (!token.IsCancellationRequested)
{
var current = sw.ElapsedMilliseconds;
var elapsed = current - previous;
if (elapsed > timeInterval)
{
Índice = Math.Max(0, Math.Min(ÍndiceMáximo, Índice + 1 * Math.Sign(_speed)));
previous = current;
}
}
}