Unity async task can't be called outside of main thread? - unity3d

I need to convert a .txt file called TheFile to a dictionary called theFileDictionary and then save it to a binary file.
I am doing this with the following class FileHandler:
public class FileHandler : MonoBehaviour
{
[SerializeField] TextAsset theFile;
FileStream file;
public async Task LoadTheFileIntoBinaryFile()
{
BinaryFormatter formatter = new();
file = File.Create(Application.persistentDataPath + "/foods.txt");
/*line 33, error gets called*/ await Task.Run(() => { formatter.Serialize(file, LoadTheFileIntoDictionary()); } );
file.Close();
}
Dictionary<string, string[]> LoadTheFileIntoDictionary()
{
/*line 39, error occurs*/Dictionary<string, string[]> theFileDictionary =
theFile
.text
.Split('\n')
.Select(x => x.Split('\t'))
.ToDictionary(x => x[ApplicationInfo.GetLanguageIndex()].Replace("-", " "));
return blsDictionary;
}
}
When starting LoadTheFileIntoBinaryFile() I get this error:
UnityException: get_bytes can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
UnityEngine.TextAsset.get_text () (at /Users/bokken/buildslave/unity/build/Runtime/Export/Scripting/TextAsset.cs:23)
FileHandler.LoadTheFileIntoDictionary () (at Assets/Scripts/Daten/FileHandler.cs:39)
FileHandler+<>c__DisplayClass3_0.b__0 () (at Assets/Scripts/Daten/FileHandler.cs:33)
System.Threading.Tasks.Task.InnerInvoke () (at :0)
System.Threading.Tasks.Task.Execute () (at :0)
--- End of stack trace from previous location where exception was thrown ---
From what the error says it seems to be that the Dictionary has to be initialised in Awake() or Start(). When I do that it kinda works but I don't need the method in Start(), I need it sometimes later (and I still need it to load asynchronously)...
I tried declaring and implementing the Dictionary<string, string[]> in Start() but with no change in result.
What am I doing wrong?

Related

Unity3d JsonUtility.FromJson() read from TextAsset works in app, fails in Test Runner

The following code works fine in my application (Unity 2019.3.0f6). It reads from Assets/Resources/lesson-text.json and writes the expected logs to the console:
// file to read lessons from
public TextAsset jsonFile;
internal JsonLessonList LoadLessonFromFile()
{
JsonLessonList testLessonList = JsonUtility.FromJson<JsonLessonList>(jsonFile.text);
foreach (JsonLesson lesson in testLessonList.jsonLessonList)
{
Debug.Log("Found lesson: " + lesson.Name);
}
return testLessonList;
}
I'm wanting to read the same file when using Unity's Test Runner:
[UnityTest]
public IEnumerator TestFileParsesOkTest()
{
JsonLessonList testLessonList = jsonReader.LoadLessonFromFile();
Assert.IsNotNull(testLessonList);
yield return null;
}
but I keep getting this exception:
TestFileParsesOkTest (0.019s)
Unhandled log message: '[Exception] NullReferenceException: Object reference not set to an instance of an object'. Use UnityEngine.TestTools.LogAssert.Expect
JsonReader.LoadLessonFromFile () (at Assets/Scripts/JsonReader.cs:68)
JsonReader.Start () (at Assets/Scripts/JsonReader.cs:37)
NullReferenceException: Object reference not set to an instance of an object
I know the file format is ok because it works from the app. I think the problem is that "TextAsset jsonFile" that is set through the unity editor is not being seen by the Test Runner. How do I make this work?
[Test]
public void JsonFileResourceTest()
{
Assert.IsNotNull(jsonReader.jsonFile);
}
results in:
JsonFileResourceTest (0.020s)
Expected: not null
But was: null
(The test driven development tag is because I got the very simplest read of a file with one field working, and now I want to back up and write a unit test for it and then write tests before coding going forward.)
I figured it out:
[SetUp]
public void Setup()
{
jsonReader = new GameObject().AddComponent<JsonReader>();
jsonReader.jsonFile = Resources.Load("lesson-test") as TextAsset;
}
// Verify class exists
[Test]
public void JsonReaderClassExists()
{
Assert.IsNotNull(jsonReader);
Assert.IsNotNull(jsonReader.jsonFile);
}

Vertx: Using AbstractVerticle Context to pass around objects

We have been using the Context object to in a long chain of async execution.
e.g.:
private void checkVehicle(final JsonObject cmd,
final RedisFleetStorage storage,
final Handler<AsyncResult<String>> handler) {
// omitted for brevity
// some async call to another verticle
storage.getVehicle(fleetId, vehicleId, result -> {
if (!result.succeeded()) {
LOG.error(String.format("Impossible to get vehicleStatus %s:%s", fleetId, vehicleId), result.cause());
handler.handle(Future.failedFuture("KO");
return;
}
// put vehicle in context for later use
final Vehicle vehicle = result.result();
LOG.info("vehicle details {}", vehicle);
context.put("vehicle", vehicle);
handler.handle(Future.succeededFuture());
});
}
As seen above, we put an object (vehicle) in the context and then access later in the execution.
But we suspect that the vehicle object it's altered by another execution. Is it possible? Can another event-loop change the object in the context?
A verticle instance handles all requests with the same event loop.
This why the Context object is not suited for storage of request specific data.

Valac won't let me "install_style_property" from a static method

I would like to use Gtk.Widget's ìnstall_style_property () on a widget I'm writing. In the docs, this method is declared as static, so I am wondering why valac still complains that I am calling it from a static method:
public class MyClass : Gtk.Widget {
public static void init () {
ParamSpecDouble _the_property = new ParamSpecDouble
(
"dummy", "dummy", "dummy,
0, double.MAX, 0,
ParamFlags.READWRITE | ParamFlags.STATIC_STRINGS
);
install_style_property (_the_property);
}
}
void main (string? argv) {
Gtk.init (ref argv);
MyClass.init ();
}
The error message:
test.vala:11.9-11.46: error: Access to instance member `Gtk.Widget.install_style_property' denied
If this does not work, what is the preferred pattern to install custom style properties to a custom widget in Gtk? Personally, I would prefer not to have to call an init () before using my widget, but as adding style properties is done per-class instead of per-instance, putting it into the constructor does not seem right, either.
install_style_property() is not static; it's actually a class method. valadoc.org is showing static for some reason; you'll probably have to report that as a bug (if it hasn't already).
class methods operate on a class itself. GObject classes have shared metadata, and these methods modify that metadata. Such metadata should only be modified when the class is first initialized; therefore, the methods should only be called within that class's GObjectClass.class_init() method. In Vala, this is the static construct method:
public class MyClass : Gtk.Widget {
static construct {
ParamSpecDouble _the_property = new ParamSpecDouble
(
"dummy", "dummy", "dummy,
0, double.MAX, 0,
ParamFlags.READWRITE | ParamFlags.STATIC_STRINGS
);
install_style_property (_the_property);
}
}

Check if spawn is empty

I have two spawn spots where a player will show up upon connection.
I need that, when one of the players connects on one of the spots, any other player will always spawn at the other spot.
Here's some visual in case it helps: https://goo.gl/Y0ohZC
Here is the code I'm using:
using UnityEngine;
using System.Collections;
public class SpawnSpot : MonoBehaviour {
public int teamId=0;
public GameObject[] Spots; //Drag the spots in here (In the editor)
bool[] OccupiedSpawnSpots;
//Using Photon Networking
void OnJoinedRoom()
{
//Request the recent OccupiedSpawnSpots List
PhotonView.RPC("RequestList", PhotonTargets.MasterClient, PhotonNetwork.player);
}
//In "RequestList" the MasterClient sends his List of the SpawnSpots
//by calling "ReceiveList"
[RPC]
void RequestList(PhotonPlayer player)
{
PhotonView.RPC("ReceiveList", PhotonTargets.All, player, OccupiedSpawnSpots);
}
[RPC]
void ReceiveList(PhotonPlayer Sender, bool[] ListOfMasterClient)
{
OccupiedSpawnSpots = ListOfMasterClient;
//Get the free one
if (OccupiedSpawnSpots[0] == false)
{
//Spawn player at 0
if (Sender == PhotonNetwork.player)
PhotonNetwork.Instantiate("PlayerController", Spots[0].transform.position);
OccupiedSpawnSpots[0] = true;
}
else
{
//Spawn player at 1
if (Sender == PhotonNetwork.player)
PhotonNetwork.Instantiate("PlayerController", Spots[1].transform.position);
OccupiedSpawnSpots[1] = true;
}
}
The errors given are:
Assets/Scripts/SpawnSpot.cs(14,28): error CS0120: An object reference
is required to access non-static member `PhotonView.RPC(string,
PhotonPlayer, params object[])'
Assets/Scripts/SpawnSpot.cs(22,28): error CS0120: An object reference
is required to access non-static member `PhotonView.RPC(string,
PhotonPlayer, params object[])'
Assets/Scripts/SpawnSpot.cs(36,47): error CS1501: No overload for
method Instantiate' takes3' arguments
Assets/Scripts/SpawnSpot.cs(43,47): error CS1501: No overload for
method Instantiate' takes3' arguments
Thanks in advance, IC
It appears that you are trying to call an instance function using a static reference. instead of doing PhotonView.RPC("RequestList", PhotonTargets.MasterClient, PhotonNetwork.player);
you need to create a reference to an PhatorView object, and then call the RPC function on it.
public PhotonView photonView;
void OnJoinedRoom()
{
if(photonView == null)
{
photonView = GetComponent<PhotonView>();
}
//Request the recent OccupiedSpawnSpots List
PhotonView.RPC("RequestList", PhotonTargets.MasterClient, PhotonNetwork.player);
}
you should have a PhotonView object on the same GameObject that this script is on, or assign a reference to a PhotonView in the editor.
That should fix your problems, but I think you should look into compiler errors and how to to fix them. In Unity3D if you double click the error in the console it will take you to the line that isn't compiling. It also tends to give you a pretty good hint as to why it isn't compiling.
your error was this
"Assets/Scripts/SpawnSpot.cs(14,28): error CS0120: An object reference is required to access non-static member `PhotonView.RPC(string, PhotonPlayer, params object[])'"
which means that you need an object in order to call this function.
The third and fourth errors are because you are calling Instantiate with only 2 arguments, but it takes at least 4. Include the rotation of the GameObject you're trying to instantiate, and a group number:
PhotonNetwork.Instantiate("PlayerController", Spots[0].transform.position, Quaternion.identity, 0);
Note that you may not want the identity quaternion, so you may need to change this. Also I'm not familiar with PhotonNetwork so 0 may not be an advisable group.

What's the best way to get a return value out of an asyncExec in Eclipse?

I am writing Eclipse plugins, and frequently have a situation where a running Job needs to pause for a short while, run something asynchronously on the UI thread, and resume.
So my code usually looks something like:
Display display = Display.getDefault();
display.syncExec(new Runnable() {
public void run() {
// Do some calculation
// How do I return a value from here?
}
});
// I want to be able to use the calculation result here!
One way to do it is to have the entire Job class have some field. Another is to use a customized class (rather than anonymous for this and use its resulting data field, etc.
What's the best and most elegant approach?
I think the Container above is the "right" choice. It could be also be genericized for type safety. The quick choice in this kind of situation is the final array idiom. The trick is that a any local variables referenced from the Runnable must be final, and thus can't be modified. So instead, you use a single element array, where the array is final, but the element of the array can be modified:
final Object[] result = new Object[1];
Display display = Display.getDefault();
display.syncExec(new Runnable()
{
public void run()
{
result[0] = "foo";
}
}
System.out.println(result[0]);
Again, this is the "quick" solution for those cases where you have an anonymous class and you want to give it a place to stick a result without defining a specific Container class.
UPDATE
After I thought about this a bit, I realized this works fine for listener and visitor type usage where the callback is in the same thread. In this case, however, the Runnable executes in a different thread so you're not guaranteed to actually see the result after syncExec returns. The correct solution is to use an AtomicReference:
final AtomicReference<Object> result = new AtomicReference<Object>();
Display display = Display.getDefault();
display.syncExec(new Runnable()
{
public void run()
{
result.set("foo");
}
}
System.out.println(result.get());
Changes to the value of AtomicReference are guaranteed to be visible by all threads, just as if it were declared volatile. This is described in detail here.
You probably shouldn't be assuming that the async Runnable will have finished by the time the asyncExec call returns.
In which case, you're looking at pushing the result out into listeners/callbacks (possibly Command pattern), or if you do want to have the result available at a later in the same method, using something like a java.util.concurrent.Future.
Well, if it's sync you can just have a value holder of some kind external to the run() method.
The classic is:
final Container container = new Container();
Display display = Display.getDefault();
display.syncExec(new Runnable()
{
public void run()
{
container.setValue("foo");
}
}
System.out.println(container.getValue());
Where container is just:
public class Container {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object o) {
value = o;
}
}
This is of course hilarious and dodgy (even more dodgy is creating a new List and then setting and getting the 1st element) but the syncExec method blocks so nothing bad comes of it.
Except when someone comes back later and makes it asyncExec()..