Unity Editor detect when build fails - unity3d

I'm trying to use IPreProcessBuildWithReport and IPostProcessBuildWithReport to add a game object to the scene right before build, and promptly delete it from the scene when the build finishes. The goal is to reduce all that I can from the hierarchy during the development but include these objects during the build.
My issue occurs when the build fails (due to some error), it adds the game object before the build started but never reached the code to delete the game object when it fails.
Is there a similar method to detect when the build fails without building my own build player pipeline? I really don't want to stray away from unity's default build pipeline if I don't need to.
If there was no easy way, I was trying to think if maybe there was a way to track the buildfailed exception or error and run a method if it occurs.
Thanks, let me know what you guys think.
EDIT: As requested, here is an example:
class CustomBuildPipeline : MonoBehaviour, IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
public int callbackOrder => 0;
// CALLED BEFORE THE BUILD
public void OnPreprocessBuild(BuildReport report)
{
// Create and add gameobjects to scene
}
// CALLED AFTER THE BUILD
public void OnPostprocessBuild(BuildReport report)
{
// Remove gameobjects from scene once built
}
}
}
PreProcess Interface
PostProcess Interface
I was wondering if there was a possible solution by somehow detecting when this buildexception was thrown and running code following.
EDIT 2: I found this post that has possible solutions, however, it may not be as elegant as I'd prefer. I'll try some things out and report back.

I used the post in the 'EDIT 2' to come up with a decent solution. I don't know if it will work 100% of the time and I would love for someone to correct me if I have chosen a poor solution. This should allow me to run code before the build starts, and if the build fails or succeeds without changing the unity build pipeline.
class CustomBuildPipeline : MonoBehaviour, IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
public int callbackOrder => 0;
// CALLED BEFORE THE BUILD
public void OnPreprocessBuild(BuildReport report)
{
// Start listening for errors when build starts
Application.logMessageReceived += OnBuildError;
}
// CALLED DURING BUILD TO CHECK FOR ERRORS
private void OnBuildError(string condition, string stacktrace, LogType type)
{
if (type == LogType.Error)
{
// FAILED TO BUILD, STOP LISTENING FOR ERRORS
Application.logMessageReceived -= OnBuildError;
}
}
// CALLED AFTER THE BUILD
public void OnPostprocessBuild(BuildReport report)
{
// IF BUILD FINISHED AND SUCCEEDED, STOP LOOKING FOR ERRORS
Application.logMessageReceived -= OnBuildError;
}
}
}

This approach is using a custom build handler, but still uses the default build pipeline.
Use BuildHandler.OnBuildStarted instead of (or in addition to) IPreprocessBuildWithReport for pre-processing. For post-processing use BuildHandler.OnBuildCleanup, which is called when a build completes or fails. Can also be used in conjunction with IPostprocessBuildWithReport.
[InitializeOnLoad]
public class BuildHandler : MonoBehaviour
{
public static event Action OnBuildStarted;
public static event Action OnBuildCleanup;
static BuildHandler()
{
BuildPlayerWindow.RegisterBuildPlayerHandler(Build);
}
private static void Build(BuildPlayerOptions buildOptions)
{
try
{
BuildStarted();
//now start Unity's default building procedure
BuildPlayerWindow.DefaultBuildMethods.BuildPlayer(buildOptions);
}
catch (BuildPlayerWindow.BuildMethodException) { } //logged internally
catch (Exception ex)
{
Exception log = ex.InnerException == null ? ex : ex.InnerException;
Debug.LogException(log);
Debug.LogErrorFormat("{0} in BuildHandler: '{1}'", log.GetType().Name, ex.Message);
}
finally
{
BuildCleanup();
}
}
private static void BuildStarted()
{
try
{
if (OnBuildStarted != null)
OnBuildStarted.Invoke();
}
catch (Exception ex)
{
throw new Exception("OnBuildStarted failed", ex);
}
}
private static void BuildCleanup()
{
if (OnBuildCleanup != null)
{
foreach (Action cleanupBuild in OnBuildCleanup.GetInvocationList())
{
try
{
cleanupBuild.Invoke();
}
catch (Exception ex)
{
Object context = cleanupBuild.Target as Object;
Debug.LogException(ex, context);
Debug.LogWarning("Caught exception while BuildHandler.OnBuildCleanup... Continuing");
}
}
}
}
}

Related

Referencing to gameobject's renderer material after being instantiated from downloaded asset bundle not working

Hi! If you want to save time and still help please read this section and the last one to get glimpse of my problem (part 1 and 6). So much code was needed to fully present problem
Using Unity 2019.3.0b2
1.
Im creating WebGL application that allows you to customize your character via assets from downloaded asset bundles. So far I got downloading and instantiating work, but I also want to change downloaded gameobject material to custom color from color-picker. In this case I need to refer to adequate Renderer. I've got function that sends request and it goes like so:
private IEnumerator SendRequestCoroutine(UnityWebRequest request, UnityAction<UnityWebRequest> OnDownloadCompleteHandler, UnityAction<float> OnDownloadProgressHandler = null)
{
request.SendWebRequest();
while(!request.isDone)
{
if(OnDownloadProgressHandler != null)
OnDownloadProgressHandler.Invoke(request.downloadProgress);
yield return null;
}
// Has to fire once more because progress stops at around 0.87,
// never returning 1 unless download is finished.
if (OnDownloadProgressHandler != null)
OnDownloadProgressHandler.Invoke(1);
OnDownloadCompleteHandler.Invoke(request);
}
2.
I fire this coroutine like that:
public void DownloadAssetBundle(string url, ProgressBar bar = null)
{
if (isRequestSend)
{
Alerter.ShowMessage("Request has been already send, please wait untill complete.");
return;
}
UnityWebRequest request = HttpService.Instance.GetAssetBundleRequest(url);
if(bar != null)
{
HttpService.Instance.SendDownloadRequest
(
request,
(rq) => { OnDownloadAssetBundleCompleteHandler(rq); },
(rq) => OnDownloadProgressHandler(rq, bar)
);
isRequestSend = true;
}
else
{
HttpService.Instance.SendDownloadRequest
(
request,
(rq) => { OnDownloadAssetBundleCompleteHandler(rq); }
);
isRequestSend = true;
}
}
3.
OnDownloadAssetBundleCompleteHandler looks like this:
//Function that will handle asset bundle when completed.
private void OnDownloadAssetBundleCompleteHandler(UnityWebRequest request)
{
isRequestSend = false;
if(request.isHttpError || request.isNetworkError)
{
//Handle downloading error
Alerter.ShowMessage("Seems like there was a problem with downloading, try again.");
}
else
{
AssetBundle bundle;
//Handle content update
bundle = DownloadHandlerAssetBundle.GetContent(request);
AssetBundleInfo assetBundleInfo = bundle.LoadAllAssets<AssetBundleInfo>().FirstOrDefault();
if (assetBundleInfo == null)
{
//Handle error
Alerter.ShowMessage("Couldn't read information about this Character Part. AssetBundleInfo null exception.");
bundle.Unload(false);
return;
}
GameObject goToLoad = null;
goToLoad = bundle.LoadAsset<GameObject>(assetBundleInfo.ObjectName);
if (goToLoad == null)
{
Alerter.ShowMessage("Couldn't read information about this Character Part. Downloaded asset's gameobject null exception.");
bundle.Unload(false);
return;
}
Sticher.ConnectComponent(goToLoad, assetBundleInfo.PartType);
ColorSetter.Instance.ChangeSelectedBodyPart(assetBundleInfo.PartType);
bundle.Unload(false);
}
}
4.
Now the final step is to set adequate transform so my script will search for component of type Renderer, get its material of index 0 as current material to modify, class that contain ChangeSelectedBodyPart function looks like so:
using Assets.Scripts.Models.Enums;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ColorSetter : MonoBehaviour
{
public Renderer rend;
public ColorPicker picker;
public static ColorSetter Instance;
private void Awake()
{
if(Instance != null)
{
Destroy(this);
}
else
{
Instance = this;
DontDestroyOnLoad(this);
}
}
// Start is called before the first frame update
void Start()
{
picker.onValueChanged.AddListener(color =>
{
if (rend == null)
return;
rend.material.SetColor("_BaseColor", color);
}
);
}
public void ChangeSelectedBodyPart(AvatarPartType p)
{
switch(p)
{
case AvatarPartType.ClothesUpper:
SetActiveMaterial(Sticher.Instance.Clothes_Upper);
break;
case AvatarPartType.ClothesLower:
SetActiveMaterial(Sticher.Instance.Clothes_Lower);
break;
case AvatarPartType.Shoes:
SetActiveMaterial(Sticher.Instance.Shoes);
break;
case AvatarPartType.Hair:
SetActiveMaterial(Sticher.Instance.Hair);
break;
case AvatarPartType.Skin:
SetActiveMaterial(Sticher.Instance.Skin);
break;
}
}
private void SetActiveMaterial(Transform parent)
{
rend = parent.GetComponentInChildren<Renderer>();
}
}
5.
PS. parent has only one child that contains Renderer component
Now, finally, problem is that I don't get proper material reference, I got the old one that is being set via toggle button, simply as that:
public void OnValueChanged(bool value)
{
if(value)
{
ColorSetter.Instance.ChangeSelectedBodyPart(PartType);
ButtonManager.Instance.RemoveAllButtonsFromPartsWindow();
ButtonManager.Instance.PopulatePartsPanelWithAvatarPartsOfType(PartType);
}
}
6.
So in conclusion when I press on "toggle button" that represents some avatar body/clothing part it sets its parent and material properly via function, even after asset bundle has been downloaded (but I have to click the same toggle again to make it work), but when I fire the same function in OnDownloadAssetBundleCompleteHandler just after asset been downloaded it doesn't work :S Why? Is it related with asset unloading speed? Any tips on fixing this?
In Game View it behave like that:
I fixed it. Since you can't use DestoyImmediate like #derHugo said because it can cause reference errors or even it could destroy assets permamently I had to use Destroy() instead before Instantiating new gameobject from assetbundle, however Destroy() will delete given object at the end of the frame while I try to access Renderer component on freshly instantiated GameObject just at the same frame, before old one is acutally destoryed. I fixed it yielding one frame using yield return new WaitForEndOfFrame(); just after function that takes care of destroying old GameObjects before trying to access new GameObject.

class member returns null after osgi bind method

My problem is that in the main class I have some osgi references that work just fine when the class is call. But after that all the references became null. When I close the main windows and call shutdown method, the hubService reference returns null. What do I do wrong here?
private void shutdown() {
if(hubService == null) {
throw new NullPointerException();
}
hubService.shutdownHub(); // why is hubService null?
}
// bind hub service
public synchronized void setHubService(IHubService service) {
hubService = service;
try {
hubService.startHub(PORT, authenticationHandler);
} catch (Exception e) {
JOptionPane.showMessageDialog(mainFrame, e.toString(), "Server", JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
}
// remove hub service
public synchronized void unsetHubService(IHubService service) {
hubService.shutdownHub();
hubService = null;
}
If a field can be read and written by multiple threads, you must protect access to read as well as write. Your first method, shutdown, does not protect the read of hubService so that the value of hubService can change between the first read and the second read. You don't show the declaration of the hubService field. You could make it volatile or only read when synchronized (on the same object used to synchronized when writing the field). Then your shutdown implementation could look like:
private volatile IHubService hubService;
private void shutdown() {
IHubService service = hubService; // make a copy of the field in a local variable
if (service != null) // use local var from now on since the field could have changed
service.shutdownHub();
}
I assume your shutdown method is the DS deactivate method? If so, why do you shutdown in the unset method as well in the shutdown method?
Overall the design does not seem very sound. The IHubService is used as a factory and should return some object that is then closed in the deactivate method. You made the IHubService effectively a singleton. Since it must come from another bundle, it should handle its life cycle itself.
Since you also do not use annotations, it is not clear if your set/unset methods are static/dynamic and/or single/multiple. The following code should not have your problems (exammple code with bnd annotations):
#Component public class MyImpl {
IHubService hub;
#Activate
void activate() {
hubService.startHub(PORT, authenticationHandler);
}
#DeActivate
void deactivate() {
hubService.shutdown();
}
#Reference
void setHub(IHubService hub) { this.hub = hub; }
}

How can I correctly update a progress bar for an operation of unknown duration within an Eclipse wizard?

I have implemented a wizard for my Eclipse plug-in, showing several pages. One of these pages needs some lengthy initialization, that means it consists of a SWT table, which needs to be populated by information coming from an external source. This source needs to be activated first (one single method call that returns after a couple of seconds - I can not know in advance how long it will take exactly), before it can be used as input for for the table viewer. This initialization is currently done by the table model provider when it needs to access the external source for the first time.
Therefore, when I enter the wizard page, I would like to show a dummy progress bar that just counts up for a while. My approach was the following, but unfortunately does not work at all:
private void initViewer() {
IRunnableWithProgress runnable = new IRunnableWithProgress() { // needed to embed long running operation into the wizard page
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
SubMonitor progress = SubMonitor.convert(monitor);
Thread thread = new Thread() {
#Override
public void run() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
viewer.setInput(ResourcesPlugin.getWorkspace().getRoot()); // this will make the table provider initialize the external source.
}
});
}
};
thread.start();
while(thread.isAlive()) {
progress.setWorkRemaining(10000);
progress.worked(1);
}
progress.done();
}
};
try {
getContainer().run(false, false, runnable);
} catch(Exception e) {
throw new Exception("Could not access data store", e);
}
}
This method gets then invoked when the wizard page's setVisible()-method is called and should, after a couple of seconds, set the viewer's input. This, however, never happens, because the inner-most run()-method never gets executed.
Any hints on how to deal with long-running (where an exact estimate is not available) initializations in Eclipse wizards would be very appreciated!
I have given below a simple example on how to use IRunnableWithProgress along with a ProgressMonitorDialog to perform a task of unknown quantity. To start with, have an implementation to IRunnableWithProgress from where the actual task is performed. This implementation could be an inner class.
public class MyRunnableWithProgress implements IRunnableWithProgress {
private String _fileName;
public MyRunnableWithProgress(String fileName) {
_fileName = fileName;
}
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
int totalUnitsOfWork = IProgressMonitor.UNKNOWN;
monitor.beginTask("Performing read. Please wait...", totalUnitsOfWork);
performRead(_fileName, monitor); // This only performs the tasks
monitor.done();
}
}
Now, a generic implementation to ProgressMonitorDialog can be created as below which could be used for other places where a progress monitor dialog is required.
public class MyProgressMonitorDialog extends ProgressMonitorDialog {
private boolean cancellable;
public MyProgressMonitorDialog(Shell parent, boolean cancellable) {
super(parent);
this.cancellable = cancellable;
}
#Override
public Composite createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
setCancelable(cancellable);
return container;
}
}
Having got the required implementation, the task can be invoked as below to get it processed with a progress dialog.
boolean cancellable = false;
IRunnableWithProgress myRunnable = new MyRunnableWithProgress(receivedFileName);
ProgressMonitorDialog progressMonitorDialog = new MyProgressMonitorDialog(getShell(), cancellable);
try {
progressMonitorDialog.run(true, true, myRunnable);
} catch (InvocationTargetException e) {
// Catch in your best way
throw new RuntimeException(e);
} catch (InterruptedException e) {
//Catch in your best way
Thread.currentThread().interrupt();
}
Hope this helps!
I assume the reason why it's "not working" for you is that the preparation of input is done in UI thread meaning that the progress bar cannot be updated. A better approach is to prepare input in advance and only set input to viewer after that.

GWT RunAsync - Strange behavior in release mode

As suggested in this question GWT - Where should i use code splitting while using places/activities/mappers?, I created an ActivityProxy to nest my activities.
I based my implementation on this http://code.google.com/p/google-web-toolkit/issues/detail?id=5129 (6th comment), with one modification: I added a check on the provider before calling GWT.RunAsync:
if (provider != null)
{
GWT.runAsync(new RunAsyncCallback()
{
#Override
public void onFailure(Throwable reason)
{
// ...
}
#Override
public void onSuccess()
{
ActivityProxy.this.nestedActivity = provider.create();
//...
}
});
}
But for some reason, this doesn't work in release mode: the onFailure methode is never called but my activity is never displayed the first time I use it. If I reload the place, everything display just fine.
Then I realised that doing the following solves the problem:
GWT.runAsync(new RunAsyncCallback()
{
#Override
public void onFailure(Throwable reason)
{
// ...
}
#Override
public void onSuccess()
{
if (provider != null)
{
ActivityProxy.this.nestedActivity = provider.create();
//...
}
}
});
So even if I don't understand why it works, I started using it for all my activities.
I ran into the problem again when I decided to use a generator for my ActivityProxy (to avoid writing a provider for each Activity). The synthax becomes GWT.create(ActivityProxy).wrap(MyActivity.class);
Basically, the generated code looks like this:
if (clazz.getName() == "FooClass")
{
nestedActivity = new FooClass(); //inside a RunAsync
}
if (clazz.getName() == "BarClass")
{
nestedActivity = new BarClass(); //inside a RunAsync
}
And the same problem occurs: my app fails to display my activities the first time they are used.
So simple question : "Why?"

Using IoC container as a service locator for HttpHandler

This question relates to my other post.
Ok so after a bit more messing around I decided to do it this way. Which seems to work fine when I run it, although I'm getting the following error in NUnit: Could not load file or assembly 'Castle.Core, Version=1.0.3.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) So not sure what is happening there???
Just wanted to know what others thought about the design and if there are any obvious 'no no's' or improvements. I.e. Is the constructor of the base handler a good place to instantiate the windsor component or is there a better place to do this? As I said in the original post the idea behind doing things this way was to keep the components nicely decoupled and to make unit testing easy. I should also add I'm new to unit testing, mocking. Thanks!
public abstract class BaseHttpHandler : IHttpHandler
{
private HttpContext _httpContext;
private ILogger _logger;
private IDataRepository _dataRepository;
protected HttpRequest Request { get { return _httpContext.Request; } }
protected HttpResponse Response { get { return _httpContext.Response; } }
protected bool IsRequestFromUAD { get { return Request.UserAgent == null ? false : Request.UserAgent.Equals("UAD"); } }
protected ILogger Logger { get { return _logger; } }
protected IDataRepository DataRepository { get { return _dataRepository; } }
public virtual bool IsReusable { get { return false; } }
public BaseHttpHandler()
{
var container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
_logger = container.Resolve<ILogger>();
_dataRepository = container.Resolve<IDataRepository>();
}
public void ProcessRequest(HttpContext context)
{
_httpContext = context;
ProcessRequest(new HttpContextWrapper(context));
}
public abstract void ProcessRequest(HttpContextBase context);
}
public class UADRecordHttpHandler : BaseHttpHandler
{
public override void ProcessRequest(HttpContextBase context)
{
if (IsRequestFromUAD)
{
using (var reader = new StreamReader(context.Request.InputStream))
{
string data = reader.ReadToEnd();
if (Logger != null)
Logger.Log(data);
if(DataRepository != null)
DataRepository.Write(data);
context.Response.Write(data);
}
}
else
ReturnResponse(HttpStatusCode.BadRequest);
}
}
That's a very bad thing to do, what you're doing here. You should have one instance of the container per application, while with this code you will have one per each request.
About the error in NUnit: make sure you don't have other versions of Castle assemblies in the GAC. If so, uninstall them.
About your BaseHttpHandler: the problem with this implementation is that you're creating a new container. Instead, use a single container per application, like Krzysztof said. Use a static service locator, e.g. CommonServiceLocator. (I never recommend this but it's one of the few places where it does make sense).