CymaticLabs / Unity3D.Amqp

AMQP client library for Unity 3D supporting RabbitMQ
MIT License
97 stars 21 forks source link

Fantastic beginnings - a few questions #1

Open rxix opened 7 years ago

rxix commented 7 years ago

I'm an architect using Unity to visualize kinetic facade installations, which are difficult to do in real-time in other rendering packages. This is a fantastic toolkit for me, because much of my work uses AMQP as the comms backbone for the actual installations. I also can't believe you just released it as I've been looking for this functionality for a few months to no avail.

Since there may be others like me who have very limited Unity experience, could you please help explain one thing?

Thanks! Will

meverett commented 7 years ago

That sounds like a really cool project! I'm glad you find this library. I was in a similar boat in that I needed AMQP support in Unity for a project I am working on and I could not find anything that was very complete. It also turns out there are lots of issues to sort through so I'm hoping my work there can save other people time.

I was already planning on adding an example that shows how to control an object in Unity since I figured that would be a very common use case. I just hadn't gotten around to it yet.

That said, I have just updated the project's Wiki and added a tutorial on just that. You will need to download the source or Unity asset package in the releases section for v0.1.0-beta.3 as previous releases do not include the AmqpObjectController script and demo scene which are required for the tutorial.

You can find the tutorial here.

I actually ended up fixing a bug in the process of creating the tutorial so it was a worthy exercise.

Let me know if that tutorial provides what you are looking for.

Cheers!

rxix commented 7 years ago

Thanks for the prompt reply - I've been experimenting with the upgrade for a day or so now.

I really appreciate the tone and thoroughness of the tutorial. I feel that some of the most interesting or unexpected use cases come when non-experts can apply such a powerful tool to their own areas of expertise.

Personally, I find the new approach with the object controller to be extremely helpful as all of the geometry transforms (in my case, facade behaviors) can be implemented externally, and therefore plugged into systems which already exist such as my project.

I was able to get your original approach working as well (eventually!) and I find it to also be very useful. For example, my current project sends an "openness" value to a window/panel system, which is an Arduino or similar embedded device. These devices generate their own geometric movements based only on the "openness." This means that if I choose, I can send the exact data to both Unity and my microcontrollers, which is really helpful for proof of concept and a more general plug and play ecosystem which is at the core of my work.

A few observations/questions based on a week's worth of use (sorry, pretty new to github so not sure if this is the right way to add suggestions.):

How would you approach a scenario where I've got a few hundred panels which each require their own control? These panel geometries are generated automatically, so they will have unique id's, but individual connections may quickly bog the system down. Is it possible to use the Id Filter to send a larger block of transforms (for all panels at once) and then have a single panel just use its own info?

Is it better to go ahead and split the routing within RabbitMQ itself? I'm still toying with both approaches for my real-world model, and haven't decided there either.

Thanks!

meverett commented 7 years ago

Update: You can read this if you're interested in the theory behind various approaches, but I've already added a solution and updated the object controlling tutorial with a new section covering it. You can just jump to the bottom of this thread for the conclusion and link to the tutorial update.


Thanks for the heads up on the "Write to Console" issue. I will check it out, sounds like an easy fix.

In terms of how to approach the scenario you're describing each implementation will have some different design trade-offs. If it was me I would weighing factors against each other such as:

Keep in mind what I'm about to describe definitely will stray away from the simple implementation in the AmqpObjectController script, but would rely on similar principles, just modified...

Solution A: Maximum Efficiency

In my experience the most efficient "over-the-wire" solution would be to minimize individual messages and subscriptions. In other words, have just one subscription from Unity that can receive and route all of the messages AND even better if you can send all individual message states per object in batch in a single message (as say a JSON array of each message, each with their own target IDs):

[{"id":"obj1","rotX":45}, {"id":"obj2","rotX":90}, {"id":"obj3","rotX":-15}]

That is going to scale the best if performance is an issue. On the Unity end you would have to have a single script like AmqpObjectController that would parse the JSON and then unpack all of the individual objects and then loop through them and update the matching Unity game objects' transforms. That would also require you to have access (by ID) to all of the game objects you'd need to update from that single controller script so that as you loop over the individual messages, you pull out the ID, get the Unity object with the matching ID and update its values.

Solution B: Slightly Less Complicated, Slightly Less Efficient

There is a variation of Solution A in which you don't batch all of the object updates into a single message in an array and instead you still send them as individual messages with an ID. But there is still only one subscription to one exchange/topic that receives all messages only once and then dispatches them to the correct Unity object like Solution A.

Solution C: Individual Subscriptions; Topic & Routing Key

This approach is much more simple from a Unity implementation standpoint (given that it can be done with the provided AmqpObjectController script), but it will be less efficient and will require that you have quite a bit of control on how your messages are published to RabbitMQ server (which honestly I suppose most all of these solutions do to an extent). It creates a subscription per object and there's no telling how hundreds of subscriptions is going to fair. At the very least it will delay start up time (as the AMQP client must subscribe and receive confirmation for each subscription one at a time).

The idea here is that rather than having to maintain a single, central subscription and message handler that maintains a list of all Unity game objects and their IDs, you just drop a copy of the AmqpObjectController script (or something similar) on each individual Unity object you want to control. In their subscription however, subscribe using a routing key that matches their id:

For panel ID "1":

Exchange = "panels", Routing Key = "panels.1"

For panel ID "2":

Exchange = "panels", Routing Key = "panels.2"

...

You would have to set that on the inspector values on each instance of the panel and AmqpObjectController script. Then when you publish the messages, you wouldn't need to include the id property (that the ID Filter is looking for), and leave it blank on the script as well, but you would have to publish with the correct routing key per ID so that it matches the one in the subscription (panels.1, or panels.2, etc.). Also keep in mind the routing key stuff only works with the exchange is of type 'topic' in this case.

Solution D: Individual Subscriptions; Redundant Messages

I really wouldn't recommend this, but it will work in a pinch and make as a proof-of-concept/quick-and-dirty implementation. This would be where you don't change anything from the current AmqpObjectController demo setup (you just stick to one exchange and one routing key for all, or just one exchange and no routing key). It still will generate an individual subscription per Unity game object/AmqObjectController script instance, but on top of it, all subscriptions will receive all messages!

This is pretty inefficient. Let's say you had 200 panels. Each time a single panel published an update message, all 200 subscriptions in Unity would be sent a copy of the message (individually, so yes, copying the data redundantly on the wire, yuck!). Each of their 200 message handlers would fire in Unity, but in theory only one Unity object's ID Filter would match so the rest of the 199 would ignore the message (but still receive it, and not just a shared copy, each their own copy from RabbitMQ). That means redundant data and redundant JSON parsing all over the place. Hopefully you can see why this approach is the least recommended. It is of course the most "out-of-box" given the demo files I've included in the project.

Conclusion

I would definitely recommend Solution A or Solution B for what are hopefully obvious reasons at this point. They will require you to write your own Unity script that borrows from AmqpObjectController and you would just maintain a single copy of that script.

I've got a few more bug fixes I've made to the project overall. I'll take a look at the "Write To Console" issue and consider adding in a script that can handle multiple object updates from a single subscription/instance.

meverett commented 7 years ago

I haven't updated the tutorial yet, but I have added a new set of scripts and demo scene for controlling lists of objects by ID with just one subscription and message handler.

You can find it in this release

There is a new demo scene called ObjectListControlDemo. Inside there are a few new scripts:

Basically drop one copy of the AmqpObjectListController script into your scene. And then on each individual object you would like to control drop a copy of the AmqpObjectControlReference script and set its unique AMQP ID. On start each individual AmqpObjectControlReference instance will self-register with the AmqpObjectListController script as long as it is in the scene.

Now you can control any of these registered objects in the scene by sending the message using the original JSON structure (but it must include an id property).

Assuming I've registered the AMQP IDs cube1, cube2 and cube3 in Unity:

{ "id":"cube1", "rotX": 45 }
{ "id":"cube2", "rotY": 45 }
{ "id":"cube3", "rotZ": 45 }

Will update each individual object in the demo scene separately. This lets you realize Solution B in my explanation in my last post.

Alternatively I also implemented support for Solution A by batching update messages into a single AMQP message as a JSON array:

[{ "id":"cube1", "rotX": 45 }, { "id":"cube2", "rotY": 45 }, { "id":"cube3", "rotZ": 45 }]

You don't have to do anything special other than form the message you are publishing correctly, the script will detect whether or not it is an array or single message and parse accordingly.

Check it out and see if this helps out your implementation.

I also fixed the AmqpConsole bug you noticed and more bug fixes and improvements have been added since the last release.

For now the local vs world space updates as well as whether or not to ignore Position, Rotation, and Scale are global for all messages. I think next step would be to move them off of AmqpObjectListController and onto AmqpObjectControlReference instead so each individual object can maintain its own unique preferences when processing a message.

meverett commented 7 years ago

I've appended the object controlling tutorial with information about efficiently controlling list of objects based on the discussion here:

Updated Tutorial

rxix commented 7 years ago

I've been using this latest release for a couple of weeks and am happy to report that it seems pretty robust. Using the List Controller, I've had no problem updating a few dozen objects at 100ms intervals from a RabbitMQ server running on Amazon EC2.

Again, I think the applications of this are endless - for example, I've been using it to provide real-time solar position which updates from a python script I'm running elsewhere.

I feel that for maximum flexibility, the List Controller approach should have the ability to distribute any JSON content (for example, object color or other attributes) -- but I also like the shortcut to provide direct control over object position. Perhaps there's a way to provide a simple/advanced mode here.

Now, I'm curious about sending data back out of Unity to the AMQP server. In my use case, it would be great to simulate user movement through a building by having a couple of NPC's wandering around and then being able to send their location and other info to a server for more processing.

I'm also using a Kinect elsewhere in my work to provide user interaction with architectural components - being able to capture skeleton tracking within Unity and then broadcast it would be another use case.

Again, thanks for this great work!