Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fantastic beginnings - a few questions #1

Open
rxix opened this issue Mar 20, 2017 · 6 comments
Open

Fantastic beginnings - a few questions #1

rxix opened this issue Mar 20, 2017 · 6 comments

Comments

@rxix
Copy link

rxix commented Mar 20, 2017

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?

  • How do I get a simple parameter (floating point value) from a AMQP message to a parameter (for example rotation) of an object or script?

Thanks!
Will

@meverett
Copy link
Collaborator

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
Copy link
Author

rxix commented Mar 27, 2017

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.):

  • Currently if "Write to console" is enabled in the client, the client seems to fail if there is no console prefab in the scene. This took me a while to figure out and might benefit from a failover which lets it keep running regardless.

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
Copy link
Collaborator

meverett commented Mar 28, 2017

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:

  • The overall message size and send rate (how many messages/sec * message size)
  • Performance implications in Unity of updating hundreds of different game objects
  • AMQP routing complexity and whether or not I have control over how the RabbitMQ exchanges and routing are configured on the server

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.

  • Pros
    • Most efficient implementation in terms of performance; especially from a networking perspective
    • Only requires a single exchange to publish to and a single Unity subscription to receive on
  • Cons
    • The implementation in Unity becomes a bit more complicated since you will need to be prepared to parse a JSON array and then unpack individual messages and more importantly maintain a look up of your game objects (and some how with their matching IDs) so that any random message can successfully be routed to, and update, its matching game object ID, from a single message receiver/dispatcher (which is the subscription message received handler).

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.

  • Pros
    • The benefit to this approach is that you are minimizing the number of subscriptions and therefore how much data you are sending over the wire just like Solution A
    • The Unity implementation is slightly less complicated because you don't have to unpack multiple object update states/messages per AMQP message - you can just receive one message at a time just like AmqpObjectController does
  • Cons
    • The Unity implementation is still somewhat complicated in that it requires a single global message receive handler and must be able to extract IDs from individual messages and match them up to game objects for update

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.

  • Pros
    • In theory no new scripts will need to be developed, you just drop your controller script on each individual object and setup its own unique routing key for its subscription values
    • No redundant messages are sent
  • Cons
    • the AMQP client is generating a new subscription per panel, which if there are hundreds means there will be hundreds of subscriptions as well. I don't know that this is necessarily a problem for Unity or RabbitMQ, but at the very least waiting for all of those subscriptions to be processed in Unity and return ready to receive messages might take some time and delay start up/operation of the project when playing it. And it is a bit of untested territory, you would have to test that hundreds of individual subscriptions don't cause any issues (besides a longer startup time)

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.

  • Pros
    • Works without any modification to the code I've provided
  • Cons
    • Each message will be sent needlessly an extra time over the wire for each new panel that you add that will subscribe for its own messages (that scales poorly).
    • Each panel will try to handle its redundant copy of all messages sent regardless of if the ID filter doesn't match and it ultimately ignores the update values

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
Copy link
Collaborator

meverett commented Mar 28, 2017

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:

  • AmqpObjectListController - This acts similar to AmqpObjectController but you only need one copy of this and it will manage dispatching update messages to many individual objects in the scene
  • AmqpObjectControlReference - Drop this on all of the items in your scene you want to control and set their AmqpId property in the inspector. This matches to the id property in the JSON message.

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
Copy link
Collaborator

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

Updated Tutorial

@rxix
Copy link
Author

rxix commented Apr 21, 2017

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants