Coroutine causes crash in unity - c#

I added following function to my script and it causes crash in unity.
public void AddCurrentFrameToVideo()
{
_addFrameFunctionHasBeenCalled = true;
using (var encoder = new MediaEncoder(encodedFilePath, videoAttr, audioAttr))
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
IEnumerator SetFrame()
{
yield return new WaitForSeconds(0.3f);
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
if (recordingButtonHasBeenPressed)
{
yield return StartCoroutine(SetFrame());
}
else
{
yield return null;
yield break;
}
}
IEnumerator mycoroutine;
mycoroutine = SetFrame();
if (recordingButtonHasBeenPressed)
{
StartCoroutine(mycoroutine);
}
else
{
StopCoroutine(mycoroutine);
}
}
}
I call this function in an if statement in Update function. see:
void Update()
{
_currentsframe = Time.frameCount;
if (recordingButtonHasBeenPressed)
{
if (!videoBasicFileHasBeenCreated)
{
CreateVideoBasicFile();
}
if (!_addFrameFunctionHasBeenCalled)
{
AddCurrentFrameToVideo();
}
}
}
Also I controlled recordingButtonHasBeenPressed variable in another script by a button OnClick(). see:
public void RecordVideo_OnClick()
{
if (videoIsRecording)
{
videoIsRecording = false;
videoRecordButton.image.sprite = videoButtonIsNotRecordingSprite;
_myRecorderSc.recordingButtonHasBeenPressed = false;
_myRecorderSc.videoBasicFileHasBeenCreated = false;
}
else
{
videoRecordButton.image.sprite = videoButtonIsRecordingSprite;
_myRecorderSc.recordingButtonHasBeenPressed = true;
_myRecorderSc.videoBasicFileHasBeenCreated = false;
videoIsRecording = true;
}
}
I don't know why it crashes unity. I don't think it's an infinity loop.
Also I tested a DO-While loop instead of using Croutine. see:
using (var encoder = new MediaEncoder(encodedFilePath, videoAttr, audioAttr))
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
do
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
} while (recordingButtonHasBeenPressed);
}
it causes unity crash too.
What should I do? What's wrong with it?

This
IEnumerator SetFrame()
{
yield return new WaitForSeconds(0.3f);
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
if (recordingButtonHasBeenPressed)
{
yield return StartCoroutine(SetFrame());
}
}
is a recursive call where you yield return the same routine again (which internally yield returns the same routine again etc) so it waits until all nested subroutines are finished => So at some point you will get a StackOverflow!
This is definitely a closed never ending while loop
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
do
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
} while (recordingButtonHasBeenPressed);
}
within the loop the value of recordingButtonHasBeenPressed will never be changed and Unity/your app immediately freezes forever!
What you would want to do instead would be a Coroutine like
IEnumerator SetFrame()
{
// initially wait once
yield return new WaitForSeconds(0.3f);
// simply continue to execute the routine until the record shall be stopped
while(recordingButtonHasBeenPressed)
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
// yield the next frames for 0.3 seconds before checking
// recordingButtonHasBeenPressed again
yield return new WaitForSeconds(0.3f);
}
}
You then wouldn't even need to actively stop it. All you need to do is start it and then in order to interrupt it simply set recordingButtonHasBeenPressed to false.
Do it event driven
Now in general instead of using Update and multiple controller flag bools you immediately seem to reset again once a method gets called here I would rather make the entire code event driven and called once when the button is called. This would prevent having concurrent routines running by accident and make the whole thing way better to read and maintain.
I don't know your full code but it might look something like
public void RecordVideo_OnClick()
{
// invert the toggle flag
videoIsRecording = !videoIsRecording;
// depending on the new flag value chose the sprite
videoRecordButton.image.sprite = videoIsRecording ? videoButtonIsRecordingSprite : videoButtonIsNotRecordingSprite;
if (!videoIsRecording)
{
_myRecorderSc.StopRecording();
}
else
{
_myRecorderSc.StartRecoring();
}
}
and then in the recorder script you would only need
public void StartRecording()
{
if(!recording)
{
StartCoroutine(RecorderRoutine);
}
}
public void StopRecording()
{
recording = false;
}
// flag to interrupt running record
private bool recording;
private IEnumerator RecorderRoutine()
{
// Just in case prevent concurrent routines
if(recording) yield break;
recording = true;
// initialize your file
CreateVideoBasicFile();
// initially wait once
yield return new WaitForSeconds(0.3f);
using (var encoder = new MediaEncoder(encodedFilePath, videoAttr, audioAttr))
using (var audioBuffer = new NativeArray<float>(sampleFramesPerVideoFrame, Allocator.Temp))
{
// simply continue to execute the routine until the record shall be stopped
while(recording)
{
encoder.AddFrame(tex);
encoder.AddSamples(audioBuffer);
// yield the next frames for 0.3 seconds before checking
// recordingButtonHasBeenPressed again
yield return new WaitForSeconds(0.3f);
}
}
recording = false;
}

Related

my c# code works when i replace obj.SetActive(true) with Debug.Log("hit"); Why?

I am trying to make it so when you get in a certain range of the GUI it activates. It was working perfectly when I was testing with Debug.Log("hit") however I changed it to obj.setActive and it stops working. I don't understand why this is?
here's the code:
private void Update()
{
if (IsInRange())
{
canvas.SetActive(true);
} else
{
canvas.SetActive(false);
}
}
private bool IsInRange()
{
origin = canvas.transform.position;
direction = canvas.transform.forward;
Collider[] hitColliders = Physics.OverlapSphere(origin, range, playerLayer);
foreach (var hitCollider in hitColliders)
{
if (hitCollider.name == player.name)
{
playerInRange = true;
playerInRangeF1 = true;
}
else
{
count += 1;
}
}
if (count != hitColliders.Length)
{
return true;
} else
{
return false;
}
}
I have finally figured out the answer to my problem. The script I was running was a component of the game object I was trying to hide and show. When you hide a game object it obviously stops all of its components from working. All I had to do was place the script on an object that would not be hidden.

Unity Cool Down

I'm making my first game and I have one last thing left to do. I have a shop where you can buy a lot of stuff. One of them obstacles don't spawn, but it was OP. So I decided to make a button that would do this effect for 10 seconds and have it cool down 20s before the next use. I tried the known for me methods but in this case they didn't work. I just want the boolean to change to true and after 10 seconds to false and to be cool down 20s before it can be pressed again. Any ideas?
Thanks for any help :)
EDIT:
So this is my code for on click button event:
public static bool isActive;
public Animator anim;
void use()
{
isActive = true;
anim.SetBool("isAngry", true);
}
void stop()
{
isActive = false;
anim.SetBool("isAngry", false);
}
public void clickButton()
{
StartCoroutine("start");
}
IEnumerator start ()
{
while (true)
{
use();
yield return new WaitForSeconds(10f);
stop();
}
}
and this is my code for obstacles:
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
StartCoroutine(obstaclesWave());
}
private void spawnObstacles()
{
GameObject a = Instantiate(obstaclesPrefab) as GameObject;
a.transform.position = new Vector2(Random.Range(-screenBounds.x, screenBounds.x), screenBounds.y);
}
IEnumerator obstaclesWave()
{
while (true)
{
yield return new WaitForSeconds(respawnTime);
if(usePszczoła.isActive == false)
spawnObstacles();
yield return new WaitForSeconds(respawnTime);
}
}
}
Maybe you can use Coroutine. Simple code here:
private bool isCooldown = false;
public void ActivateSkill()
{
if (!isCooldown)
{
// Activate Skill
StartCoroutine(_ActivateSkillCoroutine());
}
else
{
Debug.Log("The skill is in cooldown!");
}
}
private IEnumerator _ActivateSkillCoroutine()
{
// Sets that this skill is on cooldown.
isCooldown = true;
// activate your skill, and wait for the end of it's effect.
yield return _SkillEffect();
// then wait for it's cooldown.
yield return new WaitForSeconds(20f);
// after 20 seconds, sets that the skill is ready to use.
isCooldown = false;
}
private IEnumerator _SkillEffect()
{
// 10 seconds for your skill. Try implement it yourself!
yield return new WaitForSeconds(10f);
}
This code is mere example, so you will be able to find your own way to do this.
More references : UnityEngine.Coroutine
Edit: As I see in your code, It seems like your code is, when the button is clicked, it stops spawning and waits for 10 seconds, and enables spawning, and immediatly stops spawning for 10 seconds, which loops forever. Try this:
private bool isCooldown = false
IEnumerator start ()
{
if (isCooldown) yield break;
isCooldown = true;
use();
yield return new WaitForSeconds(10f);
stop();
yield return new WaitForSeconds(20f);
isCooldown = false;
}

How to add a resume countdown to a game in unity?

So I am making a 2D app in Unity similar to any "dodge the falling objects". I have added a pause function, but I want to add a countdown timer after you exit the pause menu. What I have right now is something like this:
public void Pressed () {
if (Time.timeScale == 0) {
pauseMenu.SetActive(false);
countdown.text = "3";
yield WaitForSeconds(1);
// repeat for 2 and 1
Time.timeScale = currentTimeScale;
} else {
Time.timeScale = 0;
pauseMenu.SetActive(true);
}
}
Any suggestions on what the proper way to code this particular countdown is, or how to just fix mine in general would be very helpful.
Thanks!
-Brandon
There are already many implementations of count down timers. For example, see this and this.
The general idea is to use a int field to save the remaining time. Or use yield WaitForSeconds(1) inside a coroutine to save the state.
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour {
void Start() {
StartCoroutine(CountDown());
}
IEnumerator CountDown() {
countdown.text = "3";
yield return new WaitForSeconds(1);
countdown.text = "2";
yield return new WaitForSeconds(1);
countdown.text = "1";
yield return new WaitForSeconds(1);
//time up, now resume the app
}
}
UPDATE
As for how to pause the app, see this and this.
Time.realtimeSinceStartup returns the time since startup, not affected by Time.timeScale.
public static class CoroutineUtilities
{
public static IEnumerator WaitForRealTime(float delay){
while(true){
float pauseEndTime = Time.realtimeSinceStartup + delay;
while (Time.realtimeSinceStartup < pauseEndTime){
yield return 0;
}
break;
}
}
}
Usabilty:
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
Then we use yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1)) instead of yield return new WaitForSeconds(1) in the code above:
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour {
void Start() {
StartCoroutine(CountDown());
}
IEnumerator CountDown() {
countdown.text = "3";
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
countdown.text = "2";
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
countdown.text = "1";
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(1));
//time up, now resume the app
}
}

Logging Window in MTA thread: Access Violation

In our app we have a tracing window that we can enable on client locations to allow some debugging, it is accessed thought a static library.
Problem is, when there are a lot of log messages going to the window it crashes with an AccessViolation error. The link of code where is crashes is the RichTextBox.AppendText(..,..,..).
Here is where we create the window.
public static void Start(Form parent)
{
if (_runningThread == null || !_runningThread.IsAlive)
{
_runningThread = new Thread(() =>
{
_traceView = new TraceView(parent) { Text = "Tracing ~ " + parent.Text };
Application.Run(_traceView);
});
_runningThread.SetApartmentState(ApartmentState.MTA);
_runningThread.Start();
}
}
and here is were we write a line to the textbox
public void Write(string line, Color color)
{
try
{
_msgQueue.Enqueue(new Tuple<string, Color>(line, color));
MethodInvoker gui = delegate
{
try
{
// Was getting an overflow so trim out some lines
if (uiTrace.Lines.Length > 5000)
{
uiTrace.Lines = new string[0];
uiTrace.SelectionStart = uiTrace.TextLength;
Application.DoEvents();
}
while (_msgQueue.Count != 0)
{
bool retry;
var count = 0;
do
{
try
{
count++;
if (_indent < 0)
_indent = 0;
var msg = _msgQueue.Dequeue();
var selectionStart = uiTrace.TextLength;
uiTrace.AppendText(string.Format("[{0}] {1}{2}", _stopwatch.ElapsedMilliseconds, string.Empty.PadLeft(_indent * 4), msg.Item1));
uiTrace.Select(selectionStart, uiTrace.TextLength);
uiTrace.SelectionColor = msg.Item2;
uiTrace.SelectionStart = uiTrace.TextLength;
uiTrace.ScrollToCaret();
retry = false;
}
catch (Exception)
{
retry = true;
}
} while (retry && count < 5);
}
}
catch (Exception)
{
// We don't care about exceptions in here, for now anyway
}
};
if (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
{
uiTrace.BeginInvoke(gui);
return;
}
gui();
}
catch (Exception)
{
// QIT_Backoffice.Processes.Errors.ErrorHandler.WriteErrorLog(Sources.SourceEnum.External, ex, "Error writing to trace");
}
}
I really have no idea how to get around this one, I thought calling BeginInvoke() is what was needed.
Looking for any help possible, or if anyone knows a third party tool that could handle this better I am happy to look at that.
Below is my modification of your logger. Note how _processing and lock are used to avoid reentrancy and protect _queue. Also, I use SynchronizationContext instead of Control.BeginInvoke to avoid any dependency on the window disposition state. TraceView can be created (with TraceView.Create) and used from any thread, but its window belongs to the parent window's thread and that's also where it's delivering text into richedit. It's possible to have a dedicated STA thread for that, but I don't feel that's necessary.
[EDITED] I've eliminated what might be a race condition in checking for _processing and added CreateOnOwnThread in case a dedicated thread for the logger UI is a requirement. I also decided to keep Application.DoEvents() for cases when Write is called from a tight loop, to keep the UI responsive.
Usage (stress-test):
private void Form1_Load(object sender, EventArgs ev)
{
var traceView = TraceView.Create(this);
for (var i = 0; i < 1000; i++)
{
var _i = i;
Task.Run(() =>
{
traceView.Write(String.Format("Line: {0}\n", _i), System.Drawing.Color.Green);
});
}
}
Implementation:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Logger
{
public partial class TraceView : Form
{
private Form _parent = null;
private SynchronizationContext _context = SynchronizationContext.Current;
private int _threadId = Thread.CurrentThread.ManagedThreadId;
private object _lock = new Object(); // sync lock to protect _queue and _processing
private Queue<Tuple<string, Color>> _queue = new Queue<Tuple<string, Color>>();
private volatile bool _processing = false; // reentracy check flag
public TraceView(Form parent)
{
_parent = parent;
InitializeComponent();
}
public static TraceView Create(Form parent)
{
TraceView view = null;
// create it on the parent window's thread
parent.Invoke(new Action(() => {
view = new TraceView(parent);
view.Show(parent);
}));
return view;
}
private void DequeueMessages()
{
// make sure we are on the UI thread
Debug.Assert(Thread.CurrentThread.ManagedThreadId == _threadId);
lock (_lock)
{
// prevent re-entracy
if (_processing)
return;
// mark the beginning of processing
_processing = true;
}
// process pending messages
for (; ; )
{
Tuple<string, Color> msg = null;
lock (_lock)
{
if (!_queue.Any())
{
// mark the end of processing
_processing = false;
return;
}
msg = _queue.Dequeue();
}
if (this.Disposing || this.IsDisposed)
{
// do not just loose messages if the window is disposed
Trace.Write(msg.Item1);
}
else
{
var selectionStart = _richTextBox.TextLength;
_richTextBox.AppendText(msg.Item1);
_richTextBox.Select(selectionStart, _richTextBox.TextLength);
_richTextBox.SelectionColor = msg.Item2;
_richTextBox.SelectionStart = _richTextBox.TextLength;
_richTextBox.ScrollToCaret();
_richTextBox.Refresh(); // redraw;
// DoEvents is required if logging from a tight loop,
// to keep the UI responsive
Application.DoEvents();
}
}
}
public void Write(string line, Color color)
{
lock (_lock)
{
_queue.Enqueue(new Tuple<string, Color>(line, color));
// prevent re-entracy
if (_processing)
return; // DequeueMessages is already in progress
}
if (Thread.CurrentThread.ManagedThreadId == _threadId)
DequeueMessages();
else
_context.Post((_) =>
{
DequeueMessages();
}, null);
}
public static TraceView CreateOnOwnThread()
{
TraceView view = null;
using (var sync = new ManualResetEventSlim())
{
// create it on its own thread
var thread = new Thread(() =>
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
view = new TraceView(null);
view.Show();
sync.Set(); // ready Write calls
Application.Run(view); // view does Application.ExitThread() when closed
return;
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
sync.Wait();
}
return view;
}
}
}
I have a lot more .Net experience with VB than C#, but doesn't the following code:
if (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
{
uiTrace.BeginInvoke(gui);
return;
}
gui();
result in gui being invoked if InvokeRequired etc. in the If statement, as well as being executed (again) in the current (presumably non-UI) thread in gui().
Wouldn't:
If (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
{
uiTrace.BeginInvoke(gui);
return;
}
Else
gui();
be more appropriate?

delegate.Invoke and delegate.BeginInvoke - used on the same method, return two different results

I'm writing a software that communicates with 4 devices via serial port. One of them, Input/output module (ICP CON) has 4 input channels (DL) and 4 output channels (RL). I need to monitor status of DL channels and then, when a signal is detected I have to do some processing, which depends on which signal has been detected.
I'm calling 4 methods asynchronously, every 500ms (timer), here's the tick event:
//stop the timer
timer1.Stop();
//open com port 2
Tester.Devices.ICP.OpenICPPort(2, 9600);
//dl 0
ic = new CheckDLStatus(0, this);
ic.Execute();
//dl 1
ic = new CheckDLStatus(1, this);
ic.Execute();
//dl 2
ic = new CheckDLStatus(2, this);
ic.Execute();
//dl3
ic = new CheckDLStatus(3, this);
ic.Execute();
//close com port 2
Tester.Devices.ICP.CloseICPPort(2);
//enable the timer again
timer1.Enabled = true;
public CheckDLStatus(int DL, Form1 F1)
{
//form 1 instance
f1 = F1;
// setup the delegate to call
switch (DL)
{
case (0):
{
checkDL_delegate = new checkDL(
BusinessLogicLayer.Classes.
DevicesCommunication.CheckDl0);
break;
}
case (1):
{
checkDL_delegate = new checkDL(
BusinessLogicLayer.Classes.
DevicesCommunication.CheckDl1);
break;
}
case (2):
{
checkDL_delegate = new checkDL(
BusinessLogicLayer.Classes.
DevicesCommunication.CheckDl2);
break;
}
case (3):
{
checkDL_delegate = new checkDL(
BusinessLogicLayer.Classes.
DevicesCommunication.CheckDl3);
break;
}
}
}
public static void CheckDl2()
{
//declare
bool currentStatus;
try
{
//input
currentStatus = DevicesCommunication.Dl_2_On;
//should be false at the start of the test,
//so when it becomes true, the change is detected immediately
//dl2?
if (ICP.LookForSignal_DL2((short)2,
Util.Classes.Util.ResolveComPortNumber(
Cache.settings.icpModulePort),
Convert.ToInt32(Cache.settings.icpModuleBaudRate)))
{
//signal detected
DevicesCommunication.Dl_2_On = true;
}
else
{
//signal not detected
DevicesCommunication.Dl_2_On = false;
}
//check, if status of DL2 has been changed
//(from true to false, from false to true)
if (currentStatus != DevicesCommunication.Dl_2_On)
{
//status from before checking signal is different
// from status read from the device so
//status has changed
if (DevicesCommunication.Dl_2_On)
{
DevicesCommunication.DL2_apperancesCounter += 1;
//TODO
//process
//ProcessDL2();
}
}
else
{
//status did not change
//just clear buffer
ClearBuffer();
}
return;
}
catch (Exception ex)
{
Util.Classes.ErrorLogging.LogError(ex, true);
//EndCurrentTest();
return;
}
}
Execute() method, that invokes a delegate:
public void Execute()
{
// call the method on the thread pool
checkDL_delegate.BeginInvoke(
this.CallBack, null);
//checkDL_delegate.Invoke();
}
The method that is called for checking DL2 status:
public static bool LookForSignal_DL2(short DL_number, int port, int baudRate)
{
//declare
bool iBit;
try
{
//check if there is a signal at specified Dl_number
iBit = DCON.Read_DI_Bit(Convert.ToByte(port), 1, -1,
DL_number, 16, 0, 100);
//return resposne
return iBit; //true/false
}
catch (Exception ex)
{
Util.Classes.ErrorLogging.LogError(ex, true);
return false;
}
}
My problem is, when I turn on signal on DL2 channel, and I call LookForSignal_DL2 like this, without the timer and asynchronous calls (just for test):
private void button25_Click(object sender, EventArgs e)
{
ICP.OpenICPPort(2, 9600);
if (ICP.LookForSignal_DL2(2, 2, 9600))
{
MessageBox.Show("True");
}
else
{
MessageBox.Show("false!");
}
ICP.CloseICPPort(2);
}
It works - returns true.
If, in Execute() method I use Invoke, which makes the method call synchronous - it works (returns true), but this way I can only check 1 signal at the time.
If, in Execute() method I use BeginInvoke it doesn't work, it returns false, even though there is signal in DL2.
I admit I don't know what's going on. Do you have any idea?
I figured it out, if it helps anyone, I made a mistake here (bold text):
//stop the timer
timer1.Stop();
//open com port 2
Tester.Devices.ICP.OpenICPPort(2, 9600);
//dl 0
ic = new CheckDLStatus(0, this);
ic.Execute();
//dl 1
ic = new CheckDLStatus(1, this);
ic.Execute();
//dl 2
ic = new CheckDLStatus(2, this);
ic.Execute();
//dl3
ic = new CheckDLStatus(3, this);
ic.Execute();
Tester.Devices.ICP.CloseICPPort(2);
I ran my methods asynchronously, but the execution of the code went on, and everytime the port had been closed before any of the methods was able to check for signal. Unfortunetely the SDK library doesn't return any error in that case, so it just kept returning false.
Thank you for your comments.

Categories