I'm new to Reactive Extensions for .NET and while playing with it I thought that it would be awesome if it could be used for games instead of the traditional update-render paradigm. Rather than trying to call Update() on all game objects, the objects themselves would just subscribe to the properties and events they are interested in and handle any changes, resulting in fewer updates, better testability and more concise queries.
But as soon as, for example, a property's value changes, all subscribed queries will also want to update their values immediately. The dependencies may be very complex, and once everything is going to be rendered I don't know whether all objects have finished updating themselves for the next frame. The dependencies may even be such that some objects are continuously updating based on each other's changes. Therefore the game might be in an inconsistent state on rendering. For example a complex mesh that moves, where some parts have updated their positions and other have not yet once rendering starts. This would not have been a problem with the traditional update-render loop, as the update phase will finish completely before rendering starts.
So then my question is: is it possible to ensure that the game is in a consistent state (all objects finished their updates) just before rendering everything?
The short answer is yes, it is possible to accomplish what you're looking for regarding game update loop decoupling. I created a proof-of-concept using Rx and XNA which used a single rendering object which was not tied in any way to the game loop. Instead, entities would fire an event off to inform subscribers they were ready for render; the payload of the event data contained all the info needed to render a frame at that time for that object.
The render request event stream is merged with a timer event stream (just an Observable.Interval timer) to synchronize renders with the frame rate. It seems to work pretty well, and I'm considering testing it out on slightly larger scales. I've gotten it to work seemingly well both for batched rendering (many sprites at once) and with individual renders. Note that the version of Rx the code below uses is the one that ships with the WP7 ROM (Mirosoft.Phone.Reactive).
Assume you have an object similar to this:
public abstract class SomeEntity
{
/* members omitted for brevity */
IList _eventHandlers = new List<object>();
public void AddHandlerWithSubscription<T, TType>(IObservable<T> observable,
Func<TType, Action<T>> handlerSelector)
where TType: SomeEntity
{
var handler = handlerSelector((TType)this);
observable.Subscribe(observable, eventHandler);
}
public void AddHandler<T>(Action<T> eventHandler) where T : class
{
var subj = Observer.Create(eventHandler);
AddHandler(subj);
}
protected void AddHandler<T>(IObserver<T> handler) where T : class
{
if (handler == null)
return;
_eventHandlers.Add(handler);
}
/// <summary>
/// Changes internal rendering state for the object, then raises the Render event
/// informing subscribers that this object needs rendering)
/// </summary>
/// <param name="rendering">Rendering parameters</param>
protected virtual void OnRender(PreRendering rendering)
{
var renderArgs = new Rendering
{
SpriteEffects = this.SpriteEffects = rendering.SpriteEffects,
Rotation = this.Rotation = rendering.Rotation.GetValueOrDefault(this.Rotation),
RenderTransform = this.Transform = rendering.RenderTransform.GetValueOrDefault(this.Transform),
Depth = this.DrawOrder = rendering.Depth,
RenderColor = this.Color = rendering.RenderColor,
Position = this.Position,
Texture = this.Texture,
Scale = this.Scale,
Size = this.DrawSize,
Origin = this.TextureCenter,
When = rendering.When
};
RaiseEvent(Event.Create(this, renderArgs));
}
/// <summary>
/// Extracts a render data object from the internal state of the object
/// </summary>
/// <returns>Parameter object representing current internal state pertaining to rendering</returns>
private PreRendering GetRenderData()
{
var args = new PreRendering
{
Origin = this.TextureCenter,
Rotation = this.Rotation,
RenderTransform = this.Transform,
SpriteEffects = this.SpriteEffects,
RenderColor = Color.White,
Depth = this.DrawOrder,
Size = this.DrawSize,
Scale = this.Scale
};
return args;
}
Notice that this object doesn't describe anything how to render itself, but only acts as a publisher of data that will be used in rendering. It exposes this by subscribing Actions to observables.
Given that, we could also have an independent RenderHandler:
public class RenderHandler : IObserver<IEvent<Rendering>>
{
private readonly SpriteBatch _spriteBatch;
private readonly IList<IEvent<Rendering>> _renderBuffer = new List<IEvent<Rendering>>();
private Game _game;
public RenderHandler(Game game)
{
_game = game;
this._spriteBatch = new SpriteBatch(game.GraphicsDevice);
}
public void OnNext(IEvent<Rendering> value)
{
_renderBuffer.Add(value);
if ((value.EventArgs.When.ElapsedGameTime >= _game.TargetElapsedTime))
{
OnRender(_renderBuffer);
_renderBuffer.Clear();
}
}
private void OnRender(IEnumerable<IEvent<Rendering>> obj)
{
var renderBatches = obj.GroupBy(x => x.EventArgs.Depth)
.OrderBy(x => x.Key).ToList(); // TODO: profile if.ToList() is needed
foreach (var renderBatch in renderBatches)
{
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
foreach (var #event in renderBatch)
{
OnRender(#event.EventArgs);
}
_spriteBatch.End();
}
}
private void OnRender(Rendering draw)
{
_spriteBatch.Draw(
draw.Texture,
draw.Position,
null,
draw.RenderColor,
draw.Rotation ?? 0f,
draw.Origin ?? Vector2.Zero,
draw.Scale,
draw.SpriteEffects,
0);
}
Note the overloaded OnRender methods which do the batching and drawing of the Rendering event data (it's more of a message, but no need to get too semantic!)
Hooking up the render behavior in the game class is simply two lines of code:
entity.AddHandlerWithSubscription<FrameTicked, TexturedEntity>(
_drawTimer.Select(y => new FrameTicked(y)),
x => x.RaiseEvent);
entity.AddHandler<IEvent<Rendering>>(_renderHandler.OnNext);
One last thing to do before the entity will actually render is to hook up a timer that will serve as a synchronization beacon for the game's various entities. This is what I think of as the Rx equivalent of a lighthouse pulsing every 1/30s (for default 30Hz WP7 refresh rate).
In your game class:
private readonly ISubject<GameTime> _drawTimer =
new BehaviorSubject<GameTime>(new GameTime());
// ... //
public override Draw(GameTime gameTime)
{
_drawTimer.OnNext(gameTime);
}
Now, using the Game's Draw method may seemingly defeat the purpose, so if you would rather avoid doing that, you could instead Publish a ConnectedObservable (Hot observable) like this:
IConnectableObservable<FrameTick> _drawTimer = Observable
.Interval(TargetElapsedTime)
.Publish();
//...//
_drawTimer.Connect();
Where this technique can be incredibly useful is in Silverlight-hosted XNA games. In SL, the Game object is unavailable, and a developer needs to do some finagling in order to get the traditional game loop working correctly. With Rx and this approach, there is no need to do that, promising a much less disruptive experience in porting games from pure XNA to XNA+SL
This is potentially quite a general question about decoupling rendering from update in a game loop. This is something that networked games have to cope with already; "how do you render something that doesn't break the player's immersion when you don't actually know what's happened yet?"
One approach to this is to 'multi buffer' the scene graph, or elements of it, and actually render an interpolated version at a higher render frame rate. You still have to identify a point in your update when everything is finished for a particular time step, but it is no longer tied to the render. Instead you copy your update results to a new scene graph instance with a time stamp and get started on the next update.
It does mean that you are rendering with a lag, so may not be suitable for all types of game.
Why don't you use some kind of IScheduler to schedule your change subscriptions. Then you could have your main game loop step your scheduler implementation 16.6 ms every frame (assuming 60fps). The idea would be that it would execute any scheduled actions due in that time, so you could still use things like delay or throttle.
Related
I'm making a game, in which I have various fields that I'd like to set target values for. For example, my Camera class has:
public double zoomLevel
Currently, if the zoomLevel is (say) 1.0 and I'd like to increase it gradually to (say) 2.0, I have the following other fields to support this:
private double targetZoomLevel
private double zoomIncrement
I then have a Camera.SetZoom(double target, double increment) method that sets a desired furure zoom level, and then a Camera.Update() method that moves the current zoom level towards the target level, using the increment.
This all works well enough, but I'd really like to implement the same behaviour for other fields (e.g. camera world position, player size, player position, etc.). Using my current method, I'd need to add 2 additional 'support' fields for each field.
I'm pretty sure that my current solution is a sub-optimal, but not sure how to go about improving this. I was thinking about implementing a Property<T> class that encapulates this behaviour for a value, but not sure how to generalise an Update() method to move the current value towards its target.
Thanks!
What you're describing sounds very much like an animation. Animations are formal concepts in a couple of frameworks (CSS and WPF come to mind).
The goal of an animation is to transition something from one value to the next.
There are a variety of ways to make that transition. Sometimes you want a 2D point to follow a Bézier curve as the curve's t variable linearly changes from 0 to 1 over some period of time. Other times you want a color to transition smoothly from red to blue by going the long way around a color wheel.
Here's an interface that can abstract over that concept:
public interface IAnimation<T>
{
T Current { get; }
void Update(double progress); // progress is a number between 0 and 1
}
For example, if you want a 2D point to animate over a sine wave 800 units wide and 2 units tall:
public sealed class Point2DAnimation : IAnimation<Point2D>
{
public Point2D Current { get; private set; }
public void Update(double progress)
{
Current = new Point2D(progress * 800, Math.Sin(progress * Math.PI * 2));
}
}
There are also a variety of ways to drive the transition. A common way to drive it is to say "I want this animation to happen as smoothly as possible over X seconds". But sometimes you might want the animation to repeat from the beginning a few more times. Or perhaps you want the animation to run forward then backward then forward and so on forever.
We could define an interface to abstract over the different ways that an IAnimation<T> can be driven. And one implementation might internally have a timer that ticks frequently enough to give the illusion of smoothness as progress goes from 0 to 1 in proportion to the amount of time that has passed at the moment of each tick.
But I want to pause here for a moment and ask a question.
What does the consumer of the abstraction need from it?
If all your code needs is read access to a T and a method called Update(), then Property<T> sounds like your ticket:
public sealed class Property<T>
{
readonly Func<T, T> _update;
public Property(T initial, Func<T, T> update)
{
Value = initial;
_update = update;
}
public T Value { get; private set; }
public void Update()
{
Value = _update(Value);
}
}
That class encapsulates a readable value and gives you an Update() method to call. No need for an IAnimation<T> interface or any other fancy trappings. Of course you'll have to instantiate instances of Property<T> with an update delegate that does what you want. But you can add some static methods somewhere to set it up in the most common ways. For example:
public static class PropertyHelpers
{
public Property<double> CreateZoomLevelProperty(double initial, double increment, double target)
{
return new Property<double>(initial, old =>
{
var #new = old + increment;
if (increment > 0 && #new > target || increment < 0 && #new < target)
return target;
else
return #new;
});
}
}
On the other hand if you want to think of the value as a stream of values controlled by something else in the code and you only need to get read access to the values when they arrive, then perhaps IObservable<T> (and the Reactive NuGet package) is what you're after. For example:
public class Camera
{
double _zoomLevel;
public IDisposable UpdateZoomLevel(IObservable<double> zoomLevels)
{
return zoomLevels.Subscribe(zoomLevel => _zoomLevel = zoomLevel);
}
}
Of course then it becomes the responsibility of other code somewhere to come up with an IObservable<double> that publishes zoom levels in the fashion that you desire.
So I suppose my answer can be summed up as this:
It depends.
Disclaimer: Perhaps none of the code above will compile
In my game I have a big catalog of gear: Armors, weapons and shields. The combinations between these can be really immense.
Besides that, the player has the option of switching in-game to a different set of armor-weapon combination. In the end to solve this, I have used the following object structure.
Whenever I switch the weapons, I activate/deactivate the necessary GameObjects. The animations are set in this way:
Now, the problem is creating the animation. I first considered pre-rendering programatically all the combinations, but my catalog is so huge, that it would create 100s, if not 1000s of animations. So I opted for a different solution. Create in playtime the animation, once I knew what gear would the player select. For that, I created a script to take care of that. The problem is that I have been using APIs from UnityEditor, and now I have realized the build will not work. Specifically because of 2 different classes: EditorCurveBinding and ObjectReferenceKeyframe.
This is a couple snippets of how I was using this classes when creating the animations:
static EditorCurveBinding GetEditorCurveBinding(string path = "")
{
EditorCurveBinding spriteBinding = new EditorCurveBinding();
spriteBinding.type = typeof(SpriteRenderer);
spriteBinding.path = path;
spriteBinding.propertyName = "m_Sprite";
return spriteBinding;
}
static ObjectReferenceKeyframe GetKeyframe(float time, Sprite sprite)
{
ObjectReferenceKeyframe keyframe = new ObjectReferenceKeyframe();
keyframe.time = time / FRAMERATE;
keyframe.value = sprite;
return keyframe;
}
Now, the problem with the Curve, I think I managed to solve, replacing it with this code, replacing EditorCurveBinding with AnimationCurve:
AnimationClip clip = ...
AnimationCurve curve = new AnimationCurve();
clip.SetCurve(path, typeof(SpriteRenderer), "m_Sprite", curve);
But I have no idea how to set the sprites for each animation. I thought that using curve.AddKeycould be helpful, but I have seen no way to add a sprite there.
How could I rewrite that code to avoid using UnityEditor?
Full code
Personally, I would completely avoid built-in Animator and Animations, since this tool is for the very narrow purpose of animating a single object in a predefined way (eg: for a cutscene).
Offtopic - performance
Besides that, the player has the option of switching in-game to a different set of armor-weapon combination. In the end to solve this, I have used the following object structure.
As you probably know this is very inefficient memory-wise and will decrease performance (disabled objects still have marginal CPU overhead). And will incread load time and instantiation time of new objects.
Can I use Animator or Animation?
Because Animator has no API to access it's internals and animation type and overall you cannot do pretty mutch nothing with it except from telling it to Play or Stop.
Since I understand that your animation is "Sprite based" and not "Transform based" any sane idea is just inefficient!
Best solution that I vow against is as follows:
Create "trigger points" that you would "animate"
Based on trigger point - change sprite
public class AnimationController : MonoBehaviour
{
public int AnimationIndex;
public Sprite[] AnimationSprites;
public SpriteRenderer SpriteRenderer;
private void Update()
{
SpriteRenderer.sprite = AnimationSprites[AnimationIndex];
}
}
Better solution?
Since we already need to have custom structure that manages our sprites we might want to optimize the whole thing, since we are not using almost any of the features of Animator we could write custom controller that would replace Animator in this case. This should improve performance significantly since Animator is very heavy!
// MonoBehaviour is optional here
public class SpriteRendererAnimationHandler
{
// More fields that would control the animation timing
public Sprite[] AnimationSprites;
public SpriteRenderer SpriteRenderer;
public void OnAnimationUpdate(int index)
{
var resolvedIndex = ResolveIndex(index);
SpriteRenderer.sprite = AnimationSprites[resolvedIndex];
}
private int ResolveIndex(int index)
{
// Resolve animation index to sprite array index
}
}
// One controller per character - or global per game that synchronize all animations to locked FPS 12.
public class AnimationController : MonoBehaviour
{
private List<SpriteRendererAnimationHandler> Handlers = new List<SpriteRendererAnimationHandler>();
public void FixedUpdate()
{
foreach (var handler in Handlers)
{
// Calculate animation index
int calculatedAnimationIndex = ...;
handler.OnAnimationUpdate(calculatedAnimationIndex);
}
}
}
Add a public field named animation index to Tomasz last example, then create the animations in animator as youd normal do for animation pieces, then animate that animation index field. simple if(currentAnimationyion != lastAnimationIndex) to check if need to send to handlers and ya rocking
Forewords
Firstly, I know posting graphical resources for codes is not encouraged in this platform. I will also post the code but, in this particular case, I think posting a video about it is much more helpful than just posting some arbitrary code because the structuring of game projects really vary depending on their requirements. However, I still respect the platform's rules so if a mod asks me to format my question according to the community rules, I can do that or they also can simply delete my question. I respect that.
The Issue
It's actually a simple issue but it's driving me crazy because of its simplicity. I just want to fade in when I load a scene and then fade out whenever I click a button. As to how I do that, this is the video about it.
To sum up, I load another scene called "Fader" which contains a ColorRect with a black color and AnimationPlayer to change ColorRect's alpha value.
The code is below with extra comments on relevant parts:
using Godot;
using System;
public class TitleScreen : Control
{
private Button[] buttons;
private Control fader; // the scene that I inject
public override void _Ready() // when title screen gets ready
{
GD.Print("Preparing TitleScreen...");
InitButtons();
InitFader(); // initialize fader
FadeIn(); // do fade in animation
}
private void InitFader() // initializing fader
{
GD.Print("Initializing fader...");
var faderScene = (PackedScene)ResourceLoader.Load("res://components/Fader.tscn"); // load external fader scene
fader = (Control)faderScene.Instance(); // instantiate the scene
fader.SetSize(OS.WindowSize); // set the size of fader scene to the game window, just in case
var rect = (ColorRect)fader.GetNode("rect"); // get "rect" child from fader scene
rect.SetSize(OS.WindowSize); // set "rect" size to the game window as well, just in case
fader.Visible = false; // set the visibility to false
AddChild(fader); // add initialized fader scene as a child of title screen
}
private void InitButtons()
{
GD.Print("Initializing buttons...");
buttons = new Button[3]{
(Button)GetNode("menu_container/leftmenu_container/menu/start_button"),
(Button)GetNode("menu_container/leftmenu_container/menu/continue_button"),
(Button)GetNode("menu_container/leftmenu_container/menu/exit_button"),
};
GD.Print("Adding events to buttons...");
buttons[0].Connect("pressed", this, "_StartGame");
buttons[2].Connect("pressed", this, "_QuitGame");
}
private void FadeIn()
{
GD.Print("Fading in...");
fader.Visible = true; // set visibility of fader to true
var player = (AnimationPlayer)fader.GetNode("player"); // get animation player
player.Play("FadeIn"); // play FadeIn animation
fader.Visible = false; // set visibility of fader to false
}
private void FadeOut()
{
// similar to FadeIn
GD.Print("Fading out...");
fader.Visible = true;
var player = (AnimationPlayer)fader.GetNode("player");
player.Play("FadeOut");
fader.Visible = false;
}
public void _StartGame() // whenever I click start game button
{
FadeOut(); // fade out
GetTree().ChangeScene("res://stages/Demo01.tscn");
}
public void _QuitGame() // whenever I click quit game button
{
FadeOut(); // fade out
GetTree().Quit();
}
}
Seems like I can't see something. Why does it not fade in and out?
Environment
Manjaro 19.0.2
Mono JIT Compiler 6.4.0 (if it is relevant)
Godot 3.2
So, the issue was Play method on AnimationPlayer object kinda runs like async (dunno if this is the correct term for it).
Luckily, there is a feature called signals in Godot. There are animation_started and animation_finished signals on AnimationPlayer objects. Basically, I created a C# script for Fader scene, hooked the signals from player to fader as in:
animation_started to _FaderAnimationStart
animation_finished to _FaderAnimationEnd
At the end, my script looks like below:
using Godot;
using System;
public class Fader : Control
{
private ColorRect rect;
private AnimationPlayer player;
public override void _Ready()
{
GD.Print("Initializing Fader...");
rect = (ColorRect)GetNode("rect");
player = (AnimationPlayer)GetNode("player");
SetSize(OS.WindowSize);
rect.SetSize(OS.WindowSize);
Visible = false;
}
private void _FaderAnimationStart(String anim_name)
{
Visible = true;
}
private void _FaderAnimationEnd(String anim_name)
{
Visible = false;
}
}
I solved it thanks to njamster's answer and Hans Passant's comment.
However, this only solves half of the problem. Yes, the scene now fades in when it loads but it does not fade out. Given that it executes kinda-async (again, I'm not sure if this is the correct term), changing scene interrupts while running the animation. I will update the answer when I solve that problem as well.
Update
Well, I cannot seem to solve the fade out part because it requires to access parent node from initialized child scene. There are some methods I can think of.
First one is to somehow parameterize "Fader" scene. This can be done in many ways but at the end, when you initialize it from another scene, you need to cast it to Fader and I don't know if this is a valid way to do it. Another concern is standardizing this in the codebase. A similar method is discussed in here.
Second one is to write it as a plugin which has it benefits and drawbacks. C# is not really battle-tested in this particular area.
Third one is to use a state management system. Here is a redux implementation for Godot. And you need to somehow integrate it for signals, which seems like a hassle.
So, overall, I still do not know how to fade out.
I recently learned about design patterns and wanted to change my code for a small game I had to make. The game is called SpaceTaxi. I've made a parser, parsing a .txt file with ascii content resulting in 4 different lists of entities: taxi, obstacle, exit and platform. Inside the gameloop I call a big collision-method using these entities detecting whether they collide. It's really ugly code. Is there a good design pattern I could use for my collision method? So instead of having a big method then have smaller classes? Currently it looks like this:
/// <summary>
/// Checks for collision with obstacles, platforms and exits.
/// </summary>
private void CollisonCheck() {
var platformCollision = Player.CollisionPlatform(Parser.PlatformEntities,
false);
var obstacleCollision = Player.CollisionObstacle(Parser.ObstacleEntities,
false);
var exitCollision = Player.CollisionObstacle(Parser.ExitEntities,
false);
// Landing on platform
if (platformCollision.Item1 && obstacleCollision) {
// Stand still on platform
if (Math.Abs(Player.Entity.Shape.AsDynamicShape().Direction.Y)
< Constants.COLLISION_DISTANCE) {
Player.Shape.Direction.Y = 0;
Player.Shape.Direction.X = 0;
Player.OnPlatform = true;
// Explode because of too much speed
} else {
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
0.1f, 0.1f);
}
// Be rewarded in case player transports a customer
if (Player.HasCostumer) {
foreach (var customer in pickedUpCustomers) {
if (CorrectDestination(platformCollision.Item2,
customer.DestinationPlatform)) {
score.AddPoint(CurrentCustomer.RewardPoints);
customer.CanRemove = true;
Player.HasCostumer = false;
}
}
}
// Exit map
} else if (exitCollision) {
// Switch from one map to another
if (GameRunning.CurrentMap == "the-beach.txt") {
GameRunning.CurrentMap = "short-n-sweet.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
// Switch from one map to another
} else {
GameRunning.CurrentMap = "the-beach.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
}
GameRunning.Timer.Restart();
Parser.Load(GameRunning.CurrentMap);
allCustomersInMap = new List<Customer>();
foreach (var c in Parser.Customer) {
allCustomersInMap.Add(new Customer(c.Key, c.Value.Item1,
c.Value.Item2, c.Value.Item3, c.Value.Item4,
c.Value.Item5));
}
// Collision with obstacle. Add explosion
} else if (obstacleCollision) {
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
Constants.EXPLOSION_WIDTH, Constants.EXPLOSION_HEIGHT);
TaxiBus.GetBus()
.RegisterEvent(GameEventFactory<object>.CreateGameEventForAllProcessors(
GameEventType.GameStateEvent, this, "CHANGE_STATE",
"MAIN_MENU", ""));
}
// Collision with taxi and customer
// CollisionCustomer returns a bool (item1) and null/customer (item2)
if (Player.CollisionCustomer(allCustomersInMap).Item1 && !Player.HasCostumer) {
var customer = Player.CollisionCustomer(allCustomersInMap).Item2;
TaxiMeterTimer = new Stopwatch();
TaxiMeterTimer.Start();
CurrentCustomer = customer;
pickedUpCustomers.Add(customer);
allCustomersInMap.Remove(customer);
CurrentCustomer.SetPosition(Constants.HIDEPOS_X, Constants.HIDEPOS_Y);
Player.HasCostumer = true;
}
}
Please note that questions like this are better suited for CodeReview as this is pretty much off topic for SO because you do not have a specific problem or exception that you are trying to resolve.
It is good practise in all applications to separate out decision logic from action logic, for a number of reasons:
Removing the implementation of actions from complex logic trees makes it easier to visualise the decision process, you assume that the actions work correctly and debug them separately
Creating single purpose action methods promotes code re-use from different decision logic trees in your program
You can easily test and debug individual actions or logic branches without disrupting or being distracted by the bigger picture.
You can more clearly document your intent with method Doc Comments
Just separating out the actions you have defined results in code similar to this:
/// <summary>
/// Checks for collision with obstacles, platforms and exits.
/// </summary>
private void CollisonCheck()
{
var platformCollision = Player.CollisionPlatform(Parser.PlatformEntities, false);
var obstacleCollision = Player.CollisionObstacle(Parser.ObstacleEntities, false);
var exitCollision = Player.CollisionObstacle(Parser.ExitEntities, false);
// Landing on platform
if (platformCollision.Item1 && obstacleCollision)
DockAtPlatform(); // Stand still on platform
else if (exitCollision)
ExitMap(); // Exit map
else if (obstacleCollision)
CollideWithObject(); // Collision with obstacle. Add explosion
// ??? Player collision can occur at a platform or in general regions in the map
if (Player.CollisionCustomer(allCustomersInMap).Item1 && !Player.HasCostumer)
PickupCustomer();
}
/// <summary>
/// Dock Player with platform as long as they are not approaching too fast.
/// Collect reward if carrying a passenger
/// </summary>
/// <remarks>If too fast, player will explode!</remarks>
private void DockAtPlatform()
{
if (Math.Abs(Player.Entity.Shape.AsDynamicShape().Direction.Y)
< Constants.COLLISION_DISTANCE)
{
Player.Shape.Direction.Y = 0;
Player.Shape.Direction.X = 0;
Player.OnPlatform = true;
// Explode because of too much speed
}
else
{
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
0.1f, 0.1f);
}
// Be rewarded in case player transports a customer
if (Player.HasCostumer)
{
foreach (var customer in pickedUpCustomers)
{
if (CorrectDestination(platformCollision.Item2,
customer.DestinationPlatform))
{
score.AddPoint(CurrentCustomer.RewardPoints);
customer.CanRemove = true;
Player.HasCostumer = false;
}
}
}
}
/// <summary>
/// Switch between Maps
/// </summary>
private void ExitMap()
{
// Switch from one map to another
if (GameRunning.CurrentMap == "the-beach.txt")
{
GameRunning.CurrentMap = "short-n-sweet.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
}
else
{
// Switch the reverse way around
GameRunning.CurrentMap = "the-beach.txt";
Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
Constants.PLAYER_ENTRYPOSITION_Y);
Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
}
GameRunning.Timer.Restart();
Parser.Load(GameRunning.CurrentMap);
allCustomersInMap = new List<Customer>();
foreach (var c in Parser.Customer)
{
allCustomersInMap.Add(new Customer(c.Key, c.Value.Item1,
c.Value.Item2, c.Value.Item3, c.Value.Item4,
c.Value.Item5));
}
}
/// <summary>
/// Show explosion because player has collided with an object, then return to the main menu
/// </summary>
private void CollideWithObject()
{
AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
Constants.EXPLOSION_WIDTH, Constants.EXPLOSION_HEIGHT);
TaxiBus.GetBus()
.RegisterEvent(GameEventFactory<object>.CreateGameEventForAllProcessors(
GameEventType.GameStateEvent, this, "CHANGE_STATE",
"MAIN_MENU", ""));
}
/// <summary>
/// Pickup a new customer, start the meter running and remove the customer from the map
/// </summary>
private void PickupCustomer()
{
var customer = Player.CollisionCustomer(allCustomersInMap).Item2;
TaxiMeterTimer = new Stopwatch();
TaxiMeterTimer.Start();
CurrentCustomer = customer;
pickedUpCustomers.Add(customer);
allCustomersInMap.Remove(customer);
CurrentCustomer.SetPosition(Constants.HIDEPOS_X, Constants.HIDEPOS_Y);
Player.HasCostumer = true;
}
Now you can focus on the individual actions and review them to see if there are cleaner ways to code their process.
A few notes to consider:
Add Explosion should accept a point object instead of passing through the X,Y coordinates, it makes the code easier to read and is a natural way to pass coordinates that always go together.
Instead of using Tuples, you should create specific data model classes to use as return values from the collision functions, it feels like over-engineering, but it helps both with documentation of your code and to understand the intent.
Takes little time to define a class to hold the collision response, with minimal documentation, instead of burying inside comments what .Item1 and .Item2 might be in terms of data type and logical meaning
Yes Tuples are awesome, in terms of rapidly smashing code solutions out, but you always have to review the code that creates the values to understand or identify the meaning behind the values, so these are usually the first to be refactored out of my prototyping code when I want to get serious about a solution.
You have a style of placing the comments for the next if block inside the previous branch code, move the comments to inside the affected if branch, or immediately above it
Or move the branch into its own method and you can use Doc comments.
If the Player object itself holds the meter variables, you could accept multiple passengers (up to the capacity of the vehicle) and collect multiple fares on delivery, your action for dropping off a customer at a platform already assumes multiple passengers.
You may have already catered for this in different ways
Final Note (a personal one)
Try to change your method logic into more cause and effect style, you gain a lot of benefits in the overall SDLC if you adopt more functional programming styles into your OO logic. Where you can, and especially for the smallest units of work, pass in the objects that will be operated on as arguments for your methods rather than referencing global objects, in this way you will find it easier to establish unit testing and code review of your individual methods.
Even though in this project you may not implement unit tests, it is a very good habit to evolve, it will make future code examples that you post on SO easier for the rest of us to debug and will allow you to be a more efficient contributor in a team or community development project.
My problem is I try to use Unity socket to implement something. Each time, when I get a new message I need to update it to the updattext (it is a Unity Text). However, When I do the following code, the void update does not calling every time.
The reason for I do not include updatetext.GetComponent<Text>().text = "From server: "+tempMesg;in the void getInformation is this function is in the thread, when I include that in getInformation() it will come with an error:
getcomponentfastpath can only be called from the main thread
I think the problem is I don't know how to run the main thread and the child thread in C# together? Or there maybe other problems.
Here is my code:
using UnityEngine;
using System.Collections;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.UI;
public class Client : MonoBehaviour {
System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
private Thread oThread;
// for UI update
public GameObject updatetext;
String tempMesg = "Waiting...";
// Use this for initialization
void Start () {
updatetext.GetComponent<Text>().text = "Waiting...";
clientSocket.Connect("10.132.198.29", 8888);
oThread = new Thread (new ThreadStart (getInformation));
oThread.Start ();
Debug.Log ("Running the client");
}
// Update is called once per frame
void Update () {
updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
Debug.Log (tempMesg);
}
void getInformation(){
while (true) {
try {
NetworkStream networkStream = clientSocket.GetStream ();
byte[] bytesFrom = new byte[10025];
networkStream.Read (bytesFrom, 0, (int)bytesFrom.Length);
string dataFromClient = System.Text.Encoding.ASCII.GetString (bytesFrom);
dataFromClient = dataFromClient.Substring (0, dataFromClient.IndexOf ("$"));
Debug.Log (" >> Data from Server - " + dataFromClient);
tempMesg = dataFromClient;
string serverResponse = "Last Message from Server" + dataFromClient;
Byte[] sendBytes = Encoding.ASCII.GetBytes (serverResponse);
networkStream.Write (sendBytes, 0, sendBytes.Length);
networkStream.Flush ();
Debug.Log (" >> " + serverResponse);
} catch (Exception ex) {
Debug.Log ("Exception error:" + ex.ToString ());
oThread.Abort ();
oThread.Join ();
}
// Thread.Sleep (500);
}
}
}
Unity is not Thread safe, so they decided to make it impossible to call their API from another Thread by adding a mechanism to throw an exception when its API is used from another Thread.
This question has been asked so many times, but there have been no proper solution/answer to any of them. The answers are usually "use a plugin" or do something not thread-safe. Hopefully, this will be the last one.
The solution you will usually see on Stackoverflow or Unity's forum website is to simply use a boolean variable to let the main thread know that you need to execute code in the main Thread. This is not right as it is not thread-safe and does not give you control to provide which function to call. What if you have multiple Threads that need to notify the main thread?
Another solution you will see is to use a coroutine instead of a Thread. This does not work. Using coroutine for sockets will not change anything. You will still end up with your freezing problems. You must stick with your Thread code or use Async.
One of the proper ways to do this is to create a collection such as List. When you need something to be executed in the main Thread, call a function that stores the code to execute in an Action. Copy that List of Action to a local List of Action then execute the code from the local Action in that List then clear that List. This prevents other Threads from having to wait for it to finish executing.
You also need to add a volatile boolean to notify the Update function that there is code waiting in the List to be executed. When copying the List to a local List, that should be wrapped around the lock keyword to prevent another Thread from writing to it.
A script that performs what I mentioned above:
UnityThread Script:
#define ENABLE_UPDATE_FUNCTION_CALLBACK
#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
public class UnityThread : MonoBehaviour
{
//our (singleton) instance
private static UnityThread instance = null;
////////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueUpdateFunc then executed from there
private static List<System.Action> actionQueuesUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesUpdateFunc to be executed
List<System.Action> actionCopiedQueueUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteUpdateFunc = true;
////////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueLateUpdateFunc then executed from there
private static List<System.Action> actionQueuesLateUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesLateUpdateFunc to be executed
List<System.Action> actionCopiedQueueLateUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteLateUpdateFunc = true;
////////////////////////////////////////////////FIXEDUPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueFixedUpdateFunc then executed from there
private static List<System.Action> actionQueuesFixedUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesFixedUpdateFunc to be executed
List<System.Action> actionCopiedQueueFixedUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteFixedUpdateFunc = true;
//Used to initialize UnityThread. Call once before any function here
public static void initUnityThread(bool visible = false)
{
if (instance != null)
{
return;
}
if (Application.isPlaying)
{
// add an invisible game object to the scene
GameObject obj = new GameObject("MainThreadExecuter");
if (!visible)
{
obj.hideFlags = HideFlags.HideAndDontSave;
}
DontDestroyOnLoad(obj);
instance = obj.AddComponent<UnityThread>();
}
}
public void Awake()
{
DontDestroyOnLoad(gameObject);
}
//////////////////////////////////////////////COROUTINE IMPL//////////////////////////////////////////////////////
#if (ENABLE_UPDATE_FUNCTION_CALLBACK)
public static void executeCoroutine(IEnumerator action)
{
if (instance != null)
{
executeInUpdate(() => instance.StartCoroutine(action));
}
}
////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////
public static void executeInUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesUpdateFunc)
{
actionQueuesUpdateFunc.Add(action);
noActionQueueToExecuteUpdateFunc = false;
}
}
public void Update()
{
if (noActionQueueToExecuteUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueUpdateFunc queue
actionCopiedQueueUpdateFunc.Clear();
lock (actionQueuesUpdateFunc)
{
//Copy actionQueuesUpdateFunc to the actionCopiedQueueUpdateFunc variable
actionCopiedQueueUpdateFunc.AddRange(actionQueuesUpdateFunc);
//Now clear the actionQueuesUpdateFunc since we've done copying it
actionQueuesUpdateFunc.Clear();
noActionQueueToExecuteUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueUpdateFunc
for (int i = 0; i < actionCopiedQueueUpdateFunc.Count; i++)
{
actionCopiedQueueUpdateFunc[i].Invoke();
}
}
#endif
////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////
#if (ENABLE_LATEUPDATE_FUNCTION_CALLBACK)
public static void executeInLateUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesLateUpdateFunc)
{
actionQueuesLateUpdateFunc.Add(action);
noActionQueueToExecuteLateUpdateFunc = false;
}
}
public void LateUpdate()
{
if (noActionQueueToExecuteLateUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueLateUpdateFunc queue
actionCopiedQueueLateUpdateFunc.Clear();
lock (actionQueuesLateUpdateFunc)
{
//Copy actionQueuesLateUpdateFunc to the actionCopiedQueueLateUpdateFunc variable
actionCopiedQueueLateUpdateFunc.AddRange(actionQueuesLateUpdateFunc);
//Now clear the actionQueuesLateUpdateFunc since we've done copying it
actionQueuesLateUpdateFunc.Clear();
noActionQueueToExecuteLateUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueLateUpdateFunc
for (int i = 0; i < actionCopiedQueueLateUpdateFunc.Count; i++)
{
actionCopiedQueueLateUpdateFunc[i].Invoke();
}
}
#endif
////////////////////////////////////////////FIXEDUPDATE IMPL//////////////////////////////////////////////////
#if (ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK)
public static void executeInFixedUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesFixedUpdateFunc)
{
actionQueuesFixedUpdateFunc.Add(action);
noActionQueueToExecuteFixedUpdateFunc = false;
}
}
public void FixedUpdate()
{
if (noActionQueueToExecuteFixedUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueFixedUpdateFunc queue
actionCopiedQueueFixedUpdateFunc.Clear();
lock (actionQueuesFixedUpdateFunc)
{
//Copy actionQueuesFixedUpdateFunc to the actionCopiedQueueFixedUpdateFunc variable
actionCopiedQueueFixedUpdateFunc.AddRange(actionQueuesFixedUpdateFunc);
//Now clear the actionQueuesFixedUpdateFunc since we've done copying it
actionQueuesFixedUpdateFunc.Clear();
noActionQueueToExecuteFixedUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueFixedUpdateFunc
for (int i = 0; i < actionCopiedQueueFixedUpdateFunc.Count; i++)
{
actionCopiedQueueFixedUpdateFunc[i].Invoke();
}
}
#endif
public void OnDisable()
{
if (instance == this)
{
instance = null;
}
}
}
USAGE:
This implementation allows you to call functions in the 3 most used Unity functions: Update, LateUpdate and FixedUpdate functions. This also allows you call run a coroutine function in the main Thread. It can be extended to be able to call functions in other Unity callback functions such as OnPreRender and OnPostRender.
1.First, initialize it from the Awake() function.
void Awake()
{
UnityThread.initUnityThread();
}
2.To execute a code in the main Thread from another Thread:
UnityThread.executeInUpdate(() =>
{
transform.Rotate(new Vector3(0f, 90f, 0f));
});
This will rotate the current Object the scipt is attached to, to 90 deg. You can now use Unity API(transform.Rotate) in another Thread.
3.To call a function in the main Thread from another Thread:
Action rot = Rotate;
UnityThread.executeInUpdate(rot);
void Rotate()
{
transform.Rotate(new Vector3(0f, 90f, 0f));
}
The #2 and #3 samples executes in the Update function.
4.To execute a code in the LateUpdate function from another Thread:
Example of this is a camera tracking code.
UnityThread.executeInLateUpdate(()=>
{
//Your code camera moving code
});
5.To execute a code in the FixedUpdate function from another Thread:
Example of this when doing physics stuff such as adding force to Rigidbody.
UnityThread.executeInFixedUpdate(()=>
{
//Your code physics code
});
6.To Start a coroutine function in the main Thread from another Thread:
UnityThread.executeCoroutine(myCoroutine());
IEnumerator myCoroutine()
{
Debug.Log("Hello");
yield return new WaitForSeconds(2f);
Debug.Log("Test");
}
Finally, if you don't need to execute anything in the LateUpdate and FixedUpdate functions, you should comment both lines of this code below:
//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
This will increase performance.
I have been using this solution to this problem. Create a script with this code and attach it to a Game Object:
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using UnityEngine;
public class ExecuteOnMainThread : MonoBehaviour {
public static readonly ConcurrentQueue<Action> RunOnMainThread = new ConcurrentQueue<Action>();
void Update()
{
if(!RunOnMainThread.IsEmpty)
{
while(RunOnMainThread.TryDequeue(out var action))
{
action?.Invoke();
}
}
}
}
Then when you need to call something on the main thread and access the Unity API from any other function in your application:
ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {
// Code here will be called in the main thread...
});
Much of the writing about threads in Unity is incorrect.
How so?
Unity is, of course, totally frame-based.
When you work in a frame-based system, threading issues are completely different.
Threading issues on a frame-based system are completely different. (In fact, often much easier to deal with.)
Let's say you have a Unity thermometer display that shows some value
Thermo.cs
So it will have a function which is called in Update, like
func void ShowThermoValue(float fraction) {
display code to show the current thermometer value
}
Recall that the "Update" function in Unity simply means "run this once each frame".
That only runs once per frame, and that's that.
(Naturally, it runs only on the "main thread". There's nothing else in Unity! There's just ... "the Unity thread"!)
Somewhere else, perhaps in "IncomingData.cs", you will have a function which handles the concept "a new value has arrived":
[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {
... ???
}
Note that, of course, that is a class function! What else can it be?
You can't "reach in to" a normal Unity function. (Such as ShowThermoValue.) That would be meaningless - it's just a function which runs once each frame.Footnote 1
Let's say: values arrive very frequently and irregularly.
Image you have some sort of scientific devices (perhaps IR thermometers) connected connected to a rack of PCs
Those electronic devices deliver new "temperature" values very often. Let's say dozens of times per frame.
So, "NewValueArrives" is being called 100s of times a second.
So what do you do with the values?
It could not be simpler.
From the arriving-values thread, all you do is ................. wait for it ............. set a variable in the component!!
WTF? All you do is set a variable? That's it? How can it be that simple?
This is one of those unusual situations:
Much of the writing on threads in Unity is, simply, completely hopeless.
Surprisingly, the actual approach is extremely simple.
It's so simple you may think you are doing something wrong!!
So have the variable ...
[System.Nonserialized] public float latestValue;
Set it from the "arriving thread" ...
[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {
ThisScript.runningInstance.latestValue = f; // done
}
Honestly that's it.
Essentially, to be the world's greatest expert at "threading in Unity" - which is, obviously, frame-based - there's nothing more to do than the above.
And whenever ShowThermoValue is called each frame ...................... simply display that value!
Really, that's it!
[System.Nonserialized] public float latestValue;
func void ShowThermoValue() { // note NO arguments here!
display code, draws a thermometer
thermo height = latestValue
}
You're simply displaying the "latest" value.
latestValue may have been set once, twice, ten times, or a hundred times that frame ............ but, you simply display whatever is the value when ShowThermoValue runs that frame!
What else could you display?
The thermometer is updating at 60fps on-screen so you display the latest value. Footnote 2
It's actually that easy. It's just that easy. Surprising but true.
#(Critical aside - don't forget that vector3, etc, are NOT Atomic in Unity/C#)
As user #dymanoid has pointed out (read the important discussion below) it's critical to remember that while float is atomic in the Unity/C# milieu, anything else (say, Vector3 etc) IS NOT ATOMIC. Typically (as in the example here) you only pass floats around from calculations from, say, native plugins, thermometers etc. But it's essential to be aware that vectors and so on are NOT atomic.
Sometimes experienced threading programmers get in a knot with a frame-based system, because: in a frame based system most of the problems caused by racetrack and locking issues ... do not exist conceptually.
In a frame-based system, any game items should simply be displaying or behaving based on some "current value," which is set somewhere. If you have info arriving from other threads, just set those values - you're done.
You can not meaningfully "talk to the main thread" in Unity because that main thread ............. is frame-based!
Most locking, blocking and racetrack issues are non-existent in the frame-based paradigm because: if you set latestValue ten times, a million times, a billion times, in one particular frame .. what can you do? .. you can only display one value during that frame!
Think of an old-fashioned plastic film. You literally just have ...... a frame, and that's it. If you set latestValue a trillion times in one particular frame, ShowThermoValue will simply display (for that 60th of a second) the one value it grabs when it is run.
All you do is: leave information somewhere, which, the frame-paradigm system will utilize during that frame if it wants to.
That's it in a nutshell.
Thus, most "threading issues" disappear in Unity.
All you can do from
other calculation threads or
from plugin threads,
is just "drop-off values" which the game may use.
That's it!
Let's consider the question title...
How do you "... call a function in the main Thread"
This is completely meaningless. The "functions" in Unity are simply functions which the frame engine runs once per frame.
You can't "call" anything in Unity. The frame engine runs a number of things (many things) once per frame.
Note that indeed threads are totally irrelevant. If Unity ran with a billion threads, or with quantum computing, it would have no bearing on anything.
You can't "call a function" in a frame-based system.
Fortunately, the approach to take is dead simple, you just set values, which the frame-based functions can look at when they want! It's really that easy.
Footnotes
1 How could you? As a thought experiment, forget about the issue that you're on a different thread. ShowThermoValue is run once a frame by the frame engine. You can't "call" it in any meaningful way. Unlike in normal OO software, you cannot, say, instantiate an instance of the class (a Component?? meaningless) and run that function - that is completely meaningless.
In "normal" threaded programming, threads can talk back and fore and so on, and in doing so you have concerns about locking, racetrack and so on. But that is all meaningless in an ECS, frame-based, system. There is nothing to "talk to".
Let's say that Unity was in fact multithreaded!!!! So the Unity guys have all of the engine running in a multithreaded manner. It wouldn't make any difference - you can not get "in to" ShowThermoValue in any meaningful way! It's a Component which the frame engine runs once a frame and that's that.
So NewValueArrives is not anywhere - it's a class function!
Let's answer the question in the headline:
"Use Unity API from another Thread or call a function in the main Thread?"
The concept is >> completely meaningless <<. Unity (like all game engines) is frame-based. There's no concept of "calling" a function on the main thread. To make an analogy: it would be like a cinematographer in the celluloid-film era asking how to "move" something actually on one of the frames.
Of course that is meaningless. All you can do is change something for the next photo, the next frame.
2 I refer to the "arriving-values thread" ... in fact! NewValueArrives may, or may not, run on the main thread!!!! It may run on the thread of the plugin, or on some other thread! It may actually be completely single-threaded by the time you deal with the NewValueArrives call! It just doesn't matter! What you do, and all you can do, in a frame-based paradigm, is, "leave laying around" information which Components such as ShowThermoValue, may use, as, they see fit.
Another solution to run code on the main thread, but without requiring a game object and MonoBehavior, is to use SynchronizationContext:
// On main thread, during initialization:
var syncContext = System.Threading.SynchronizationContext.Current;
// On your worker thread
syncContext.Post(_ =>
{
// This code here will run on the main thread
Debug.Log("Hello from main thread!");
}, null);
Use UniRx's multithreading pattern, UniTask and RxSocket together.
[SerializeField] private Text m_Text;
async UniTaskVoid Connect() {
IPEndPoint endPoint = new IPEndPoint(IPAddress.IPv6Loopback, 12345);
// Create a socket client by connecting to the server at the IPEndPoint.
// See the UniRx Async tooling to use await
IRxSocketClient client = await endPoint.ConnectRxSocketClientAsync();
client.ReceiveObservable
.ToStrings()
.ObserveOnMainThread()
.Subscribe(onNext: message =>
{
m_Text.text = message;
}).AddTo(this);
// Send a message to the server.
client.Send("Hello!".ToByteArray());
}