This question is not about what a null reference is, this question is about why a valid AssetBundle is throwing an NRE in an API I cannot step through
I have been converting my project from using the Resource folder to AssetBundles. I have tagged my scriptable objects in the "scriptableobjects" asset bundle, and I've loaded and stored that bundle in the "Assets/AssetBundles" folder.
Here the code where I build my assets:
public class AssetBundleManager
{
[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
string assetBundleDirectory = "Assets/AssetBundles";
if (!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}
//https://docs.unity3d.com/Manual/AssetBundles-Building.html
BuildPipeline.BuildAssetBundles(assetBundleDirectory,
BuildAssetBundleOptions.UncompressedAssetBundle,
BuildTarget.StandaloneWindows);
}
}
Before looking at the code below, here is proof that the AssetBundle is not null when it is going to be used:
After building the assets, I then go to actually load these assets and their resources:
public class X_AssetBundleManager : MonoBehaviour
{
private static Maps _maps;
private static AssetBundle _scriptableObjectsBundle;
public static T LoadScriptableObject<T>(string assetName) where T : ScriptableObject
{
if (_scriptableObjectsBundle == null)
{
AssetBundle _scriptableObjectsBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/scriptableobjects");
if (_scriptableObjectsBundle == null)
{
Debug.Log("Failed to load 'scriptableobjects' AssetBundle!");
return null;
}
}
if (_scriptableObjectsBundle.Contains(assetName)) // NRE HERE
{
Debug.Log("DOES CONTAIN");
}
else
{
Debug.Log("DOES NOT CONTAIN");
}
try
{
// NRE HERE
var all = _scriptableObjectsBundle.LoadAllAssets();
}
catch (Exception e)
{
Debug.LogError(e);
}
T obj = _scriptableObjectsBundle.LoadAsset<T>(assetName); // NRE HERE
if (obj == null)
{
Debug.LogError("Failed to load " + assetName + " in scriptableobjects");
}
return obj;
}
}
The _scriptableObjects variable is not null. Its a valid object. However, calling ANY api on it causes a null reference exception.
I can't even step through the code to see whats null. The AssetBundle itself is not null, but any call causes this.
Any advice on how to further debug this issue or what the problem might be?
in
AssetBundle _scriptableObjectsBundle = ...
you overrule (hide) your already existing field variable equally named _scriptableObjectsBundle ... so this outer field _scriptableObjectsBundle still remains null! and the one you actually assign only exists within the
if(_scriptableObjectsBundle == null)
block → simply remove this additional AssetBundle
_scriptableObjectsBundle = AssetBundle.LoadFromFile("Assets/AssetBundles/scriptableobjects");
Related
I'm trying to edit an existing component copier for the sake of copying the components over to a new game object all with the same name.
Components are successfully recursively returned with
static Component[] copiedComponents;
static void Copy(){
copiedComponents = UnityEditor.Selection.activeGameObject.GetComponentsInChildren<Component>();
}
pasting is done with
foreach(var targetGameObject in UnityEditor.Selection.gameObjects) {
if (!targetGameObject)
continue;
Undo.RegisterCompleteObjectUndo(targetGameObject, targetGameObject.name + ": Paste All Components");
foreach(var copiedComponent in copiedComponents) {
if (!copiedComponent)
continue;
UnityEditorInternal.ComponentUtility.CopyComponent(copiedComponent);
var targetComponent = targetGameObject.GetComponent(copiedComponent.GetType());
if (targetComponent) // if gameObject already contains the component
{
if (UnityEditorInternal.ComponentUtility.PasteComponentValues(targetComponent)) {
Debug.Log("Successfully pasted: " + copiedComponent.GetType());
} else {
Debug.LogError("Failed to copy: " + copiedComponent.GetType());
}
} else // if gameObject does not contain the component
{
if (UnityEditorInternal.ComponentUtility.PasteComponentAsNew(targetGameObject)) {
Debug.Log("Successfully pasted: " + copiedComponent.GetType());
} else {
Debug.LogError("Failed to copy: " + copiedComponent.GetType());
}
}
}
}
It correctly grabs every component of the parent and every single nested child, but when pasting it will only paste it on the selected parent.
Do I need to store it in a dictionary with keyvalues of the gameObject name and a list of the components or something like that?
Any input would be appreciated
Well you paste into
var targetComponent = targetGameObject.GetComponent(copiedComponent.GetType());
This only looks for components on this target object!
There is no bubbling down any hierarchy like GetComponentsInChildren does
You would basically have to check if the target object's hierarchy even matches at all and look for the correct child object.
For that you would need to additionally store the path to those child objects. You can use AnimationUtility.CalculateTransformPath like e.g.
using System.Linq;
...
private class ComponentInfo
{
public Component Component { get; }
public string Path { get; }
public ComponentInfo(Component component string path)
{
Component = component;
Path = path;
}
}
static ComponentInfo[] copiedComponents;
static void Copy()
{
var sourceRootObject = UnityEditor.Selection.activeGameObject;
// added 'true' to also include inactive
copiedComponents = sourceRootObject.GetComponentsInChildren<Component>(true)
// for each component store along with the path
.Select(c => new ComponentInfo(c, AnimationUtility.CalculateTransformPath(c.transform, sourceRootObject.transform)))
.ToArray();
}
And then later now that you have the paths stored you can use Transform.Find to try and find that object on the receiving side
foreach(var targetGameObject in UnityEditor.Selection.gameObjects)
{
if (!targetGameObject) continue;
Undo.RegisterCompleteObjectUndo(targetGameObject, targetGameObject.name + ": Paste All Components");
foreach(var copiedComponent in copiedComponents)
{
if (!copiedComponent.Component) continue;
// Try to find target child object
GameObject targetObject = null;
if(string.IsNullOrWhiteSpace(copiedComponent.Path))
{
// No path -> root object itself
targetObject = targetGameObject;
}
else
{
// Otherwise try and find child
var child = targetGameObject.transform.Find(copiedComponent.Path);
if(!child)
{
Debug.LogError($"Couldn't find {copiedComponent.Path} within {targetGameObject}");
continue;
}
// If you also want to actually create according children
// I will leave this as a homework for you
// basically you would need to split the path on '/' and for each of those check if
// according child exists and if not create it ;)
targetObject = child.gameObject;
}
// From now on below make sure to use 'targetObject' everywhere instead of 'targetGameObject'
UnityEditorInternal.ComponentUtility.CopyComponent(copiedComponent.Component);
var type = copiedComponent.Component.GetType();
if(targetObject.TryGetComponent(type, out var targetComponent))
{
// if gameObject already contains the component
if (UnityEditorInternal.ComponentUtility.PasteComponentValues(targetComponent))
{
Debug.Log($"Successfully pasted: {type}");
}
else
{
Debug.LogError($"Failed to copy: {type)}");
}
}
else
{
// if gameObject does not contain the component
if (UnityEditorInternal.ComponentUtility.PasteComponentAsNew(targetObject))
{
Debug.Log($"Successfully pasted: {type}");
}
else
{
Debug.LogError($"Failed to copy: {type}");
}
}
}
}
I'm creating a Revit plugin that reads and writes modelinformation to a database, and it all works fine in debug mode, but when I release the project and run Revit with the plugin outside visual studio, I'm getting an error when the plugin tries to read data from the database.
The code runs on DocumenetOpened event and looks like this:
public void application_DocumentOpenedEvent(object sender, DocumentOpenedEventArgs e)
{
UIApplication uiapp = new UIApplication(sender as Autodesk.Revit.ApplicationServices.Application);
Document doc = uiapp.ActiveUIDocument.Document;
//ModelGUID COMMAND
var command = new ModelCheckerCommandExec();
command.Execute(uiapp);
}
It then fails on the following line:
ModelsList = (DatabaseHelper.ReadNonAsync<RevitModel>())
.Where(m => m.ModelGUID == DataStores.ModelData.ModelGUID).ToList();
In this code block that gets executed:
public class ModelCheckerCommandExec : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
return Execute(commandData.Application);
}
public Result Execute(UIApplication uiapp)
{
Document doc = uiapp.ActiveUIDocument.Document;
Transaction trans = new Transaction(doc);
try
{
trans.Start("ModelGUID");
ModelGUIDCommand.GetAndSetGUID(doc);
trans.Commit();
var ModelsList = new List<RevitModel>();
ModelsList = (DatabaseHelper.ReadNonAsync<RevitModel>()).ToList();//.Where(m => m.ModelGUID == DataStores.ModelData.ModelGUID).ToList(); // Read method only finds models the are similar to the DataStore.ModelDate.DBId;
if (ModelsList.Count == 1)
{
trans.Start("DataFromDB");
doc.ProjectInformation.Name = ModelsList[0].ProjectName;
doc.ProjectInformation.Number = ModelsList[0].ModelNumber;
doc.ProjectInformation.Status = ModelsList[0].ModelStatus;
doc.ProjectInformation.IssueDate = ModelsList[0].ProjectIssueDate;
doc.ProjectInformation.ClientName = ModelsList[0].ClientName;
doc.ProjectInformation.Address = ModelsList[0].ProjectAddress;
doc.ProjectInformation.LookupParameter("Cadastral Data").Set(ModelsList[0].ProjectIssueDate);
doc.ProjectInformation.LookupParameter("Current Version").Set(ModelsList[0].CurrentVersion);
doc.ProjectInformation.BuildingName = ModelsList[0].BuildingName;
DataStores.ModelData.ModelManager1 = ModelsList[0].ModelManagerOne;
DataStores.ModelData.ModelManager1Id = ModelsList[0].ModelManagerOneId;
DataStores.ModelData.ModelManager2 = ModelsList[0].ModelManagerTwo;
DataStores.ModelData.ModelManager2Id = ModelsList[0].ModelManagerTwoId;
trans.Commit();
}
return Result.Succeeded;
}
catch (Exception ex)
{
TaskDialog.Show("Error", ex.Message);
return Result.Failed;
}
}
}
The "ReadNonAsync" method is as follows:
public static List<T> ReadNonAsync<T>() where T : IHasId
{
using (var client = new HttpClient())
{
var result = client.GetAsync($"{dbPath}{Properties.Settings.Default.CompanyName}_{typeof(T).Name.ToLower()}.json?access_token={DataStores.IdToken.UserIdToken}").GetAwaiter().GetResult();
var jsonResult = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
if (result.IsSuccessStatusCode)
{
var objects = JsonConvert.DeserializeObject<Dictionary<string, T>>(jsonResult);
List<T> list = new List<T>();
if (objects != null)
{
foreach (var o in objects)
{
o.Value.Id = o.Key;
list.Add(o.Value);
}
}
return list;
}
else
{
return null;
}
}
}
In the rest of my code I use a async Read method which works, so I'm wondering wether or not that's the issue, but Revit wont let me use an async method inside an Execute method.
How do I debug this issue correctly, and why could there be code working in debug that doesn't work in "live" versions?
I found a solution!
The issue:
The reason for the error was that when I run the software in debug-mode, a file path of "xxx.txt" finds files in the solution folder, but when I run the software "xxx.txt" points to the folder of the software and not the .dll -
So in my case it pointed to "c:\Program Files\Autodesk\Revit\2021".
The fix:
Hard coding the path, or by getting the path of the executing .dll
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
Debugging/Troubleshooting:
I found the issue by inserting dialogboxes with errormessages in all my try/catch statements.
I'm working in a school project using Unity and Firebase where I need to display the username of the current user in a Text box.
I´m already get the data from Firebase but when
I try to set the userNameText's text to it, nothing happens.
This is my code:
Variables:
private DatabaseReference reference;
private string userName;
public Text userNameText;
Initialization:
void Start()
{
//database reference (I'm hiding the project id)
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("https://projectID.firebaseio.com/");
// Get the root reference location of the database.
reference = FirebaseDatabase.DefaultInstance.RootReference;
GetUserData();
}
GetUserData():
public void GetUserData()
{
reference.Child("users").Child(PlayerPrefs.GetString("userUID")).GetValueAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.Log("something was wrong");
}
else if (task.IsCompleted)
{
Dictionary<string, object> results = (Dictionary<string, object>)task.Result.Value;
userName= (string)results["userName"];
Debug.Log(userName);
userNameText.text = userName;
}
});
}
When I run the project, it just display the userName in the console but the text box has not yet been updated.
You're going to want to move your void method into an IEnumerator and set your reference to a variable.
IEnumerator GetUserData(){
var getTask = reference.Child("users").Child(PlayerPrefs.GetString("userUID")).GetValueAsync();
yield return new WaitUntil(() => getTask.IsCompleted || getTask.IsFaulted);
if (getTask.IsCompleted)
{
Dictionary<string, object> results = (Dictionary<string, object>)task.Result.Value;
userName= (string)results["userName"];
Debug.Log(userName);
userNameText.text = userName;
}
}
And then in your start method, you can start the coroutine. I also had the same issue with my text box disappearing. If you put up some log statements, you can see the first pass through your function reads before the call to firebase is made, and sets the text to an empty string.
void Start()
{
//database reference (I'm hiding the project id)
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("https://projectID.firebaseio.com/");
// Get the root reference location of the database.
reference = FirebaseDatabase.DefaultInstance.RootReference;
StartCoroutine(GetUserData());
}
I'm new to C#, having written a little in a CMS, but my background is mostly JavaScript related. That said, I am working in the "Scripting" client in OpenText Capture Center. When executing the code below I get the error "The Name 'srfOnly' does not exist in the current context"
If I move the variable declarations to within the function, I get the same error, If I move them to them to global I get the same error but on a different line number.
How can I access the variables srfOnly and otherDocs throughout the code?
//Parameters:
//DataPool data
//ITrace trace
// Checking if condition is fulfilled.
if (checkDocuments(data))
{
// Getting batch field named 'cc_SkipValidation'.
// Setting new value.
DOKuStar.Data.Xml.Bool skipValidationField = data.RootNode.Fields["cc_SkipValidation"] as DOKuStar.Data.Xml.Bool;
bool srfOnly = false;
bool otherDocs = false;
if(otherDocs == true)
{
skipValidationField.SetValue(false);
}
if(srfOnly == true && otherDocs == false)
{
skipValidationField.SetValue(true);
skipValidationField.State = DataState.Ok;
}
}
// !!! Closing bracket is neccessary !!!
}
// ------------------ Functions
public bool checkDocuments(DataPool dataPool)
{
foreach (Document doc in dataPool.RootNode.Documents)
{
if (doc.Name == "ServiceRequestForm")
{
srfOnly = true;
}
else if (doc.Name != "ServiceRequestForm")
{
otherDocs = true;
}
}
trace.WriteInfo("Trace info for Validation of srfOnly = " + srfOnly);
trace.WriteInfo("Trace info for Validation of otherDocs = " + otherDocs);
// !!! No closing bracket needed !!!
Variables are limited in scope by where they exist in your code. If you declare a variable within an if{} block, the variable only exists inside that block. If you declare a variable inside of a class but not within a class method, the variable is accessible to every method in the class. If you want a variable to be accessible to every class with in a project, you would normally declare it in a public static class.
For example...
public static class GlobalClass
{
public static string myGlobal="";
}
class myClass
{
string myClassVariable = "";
private void method()
{
//myGlobal is accessible using this
GlobalClass.myGlobal ="some value";
//myClassVariable is accessible here
myClassVariable = "somevalue";
if(condition)
{
//myClassVariable is also accessible here
myClassVariable = "somevalue";
string ifBlockVariable = "";
}
//ifBlockVariable is not accessible here
}
}
A variable is only accessible within the current block (and the blocks within that block). If you want to access the srfOnly variable within the checkDocuments method you can pass it as a parameter:
public bool checkDocuments(DataPool dataPool, bool srfOnly)
If you want the variable to be accessible from anywhere in the class, you can declare it as a a property of the class or an instance variable as following:
private bool _srfOnly;
So! I've got a C# array.
And I've got a function that returns an element from the array, so the data from that reference can be accessed. Yay!
It would be really super awesome convenient if changing that reference then affected that original element in the array. Is this what static variables do? Is there a way to do it? How to do? For example:
Function A finds an item:
public TutorialPopupBehavior GetBehavior(string behaviorName) {
foreach(TutorialPopupBehavior beh in _tutorialItems) {
if(beh._popupName == behaviorName) {
return beh;
}
}
print ("Could not find behavior of name " + behaviorName);
return null;
}
And then returns it to function B, which then, ideally, would be able to change a property of the returned item:
public void SetTutorialItem(bool state, string itemName) {
TutorialPopupBehavior beh = GetBehavior(itemName);
if(beh == null) {
print ("No tutorial item found, so can't set it to " + state);
return;
}
//idealistic code: beh._isShown = true;
}
The _isShown property of that element would then be changed permanently in the original _tutorialItems array...how do you all accomplish this, or design differently, so as to avoid the problem? The reason I ask is because I have a number of arrays to search, and I don't want to complicate my code by asking the same class to search through the same set of arrays more than once.
public void GetBehavior(string behaviorName, ref TutorialPopupBehavior b) {
foreach(TutorialPopupBehavior beh in _tutorialItems) {
if(beh._popupName == behaviorName) {
b = beh;
Return;
}
}
print ("Could not find behavior of name " + behaviorName);
b = null;
}
Read this msdn article