Improbable Icon

Using Symlinks to speed up iteration time (Unity, Windows-only)

unity
symlinks
iteration
how-to
tips
tricks
windows

#1

Did you know you can use symlinks to improve your iteration time with Unity? With symlinks you never need to export prefabs or build players for local development and you can debug the server scene directly from the Unity editor.

This flow is experimental, so please let us know if you encounter any issues.

How to - Windows guide:

  1. Make sure your project is backed up (e.g., all changes submitted to source control).
  2. Open a CMD terminal with administrator rights at the root of your project
  3. Run:
    a. mkdir workers\UnityWorker b. move workers\unity\spatialos.UnityWorker.worker.json workers\UnityWorker c. move workers\unity workers\UnityClient d. cd workers\UnityWorker e. mklink /d ProjectSettings ..\UnityClient\ProjectSettings f. mklink /d Assets ..\UnityClient\Assets g. cd ../../ h. copy default_launch.json cloud_launch.json
  4. Open default_launch.json and change the value of num_workers to 0.
  5. Now you can open both workers\UnityClient and workers\UnityWorker in independent Unity instances.
    a. When you open the UnityWorker, make sure ‘Run in Background’ is enabled in the [Unity player preferences]
  6. To develop locally:
    a. Run spatial local launch b. Start the UnityWorker scene from the UnityWorker project Start the UnityClient scene from the UnityClient project
  7. To develop in the cloud, use cloud_launch.json.


#2

Hi there, I am trying this on OS X using ln -s for the symbolic link, but when going through your instructions, where is the directory UnityClient in workers? Are you missing a step?

Also, how do I launch two instances? Will there be two project folders now for Unity?

Or is this not possible on Mac?

Thanks!


#3

Hey @VectorXStudios,

Sorry for the delay. This is probably the fault of my bad instructions above, but the move workers\unity workers\UnityClient command is really just a rename.

After you’ve finished the setup, just open both the Unity projects at UnityClient / UnityWorker and launch each from the editor.

Should be possible on MacOS (although I haven’t actually tried it ;))


#5

move workers\unity workers\UnityClient command is really just a rename.

Probably can get away with substituting move with mv :slight_smile:


#6

On MacOS yep, but mv is not a native CMD command :wink:


#7

I think there’s a whoopsie in these instructions :smile:
e and f are identical, I assume one of them is meant to symlink the UnityClient to UnityWorker so when you make changes on Client it reflects on worker when you run the worker scene :blush:
Copy pasting your instructions I’ve only got this in my UnityWorker folder, which I’m pretty sure isn’t correct :slight_smile:

Unless you want us to run both the client and the worker out the UnityClient folder? But then I don’t see the point in making the UnityWorker folder :slight_smile:


#8

Yeah I have the same problem. Nothing exists in the folder except the json file? Unity doesn’t want to open the UnityWorker project either.


#9

I think this should be

d. cd workers\UnityWorker
e. mklink /d ProjectSettings ..\UnityClient\ProjectSettings
f. mklink /d Assets ..\UnityClient\Assets

?


#10

Yep, that’s right – updated step ‘f.’


#11

Awesome! Thanks for that dude :smile:


#12

I must be doing something wrong on the mac. The symlink doesn’t seem to be working. If you could post how to do this on the Mac I would greatly appreciate it. Currently iteration time is killing my efforts with SpatialOS.


#13

How are you doing the symlinks? Are you using ln -s ?


#14

Yes I am.


#15

Alright, so I’ve given this a go on Windows and I’m running into some problems.
First I realised that having a autohexgrid setup for load balacing and setting that to 0 workers doesn’t work, so I had to go and define my own static load balancing section like so

	"load_balancing": {
		"dynamic_loadbalancer": {
			"worker_scaler_config": {  
				"constant_config": {
					"num_workers": 0
				}
			},
			"worker_placer_config": {
				"random_params": {}
			},
			"loadbalancer_config": {
				"min_range_meters": 500.0,
				"max_range_meters": 10000.0,
				"speed_meters_per_second": 100.0,
				"expansion_time_millis": 60000
			}
		}
	},

Now I’ve got spatial local launch running without problems, I go to start my worker scene in my worker unity project and I’m getting the following error in the console windows:

ERRO [improbable.bridge.fapi.WorkerAssemblyBridgeSettingsResolver] No worker assembly found for worker type UnityWorker.

time=2017-06-20T22:22:09+01:00
ERRO [improbable.core.util.common.AnonymousFunctionSubscription] SpatialOS-internal error: Exception in callback when forwarding msg: RequestBridgeModule(UnityWorker,{},Some(RakNetLinkSettings(20 seconds,2 minutes)),UnityWorker50143290-78e3-4db8-8fcf-55c6315622ab).
java.lang.RuntimeException: Unknown engine type UnityWorker
at improbable.module.receptionist.ReceptionistActor$$anonfun$2.apply(ReceptionistActor.scala:96)
at improbable.module.receptionist.ReceptionistActor$$anonfun$2.apply(ReceptionistActor.scala:96)
at scala.Option.getOrElse(Option.scala:121)
at improbable.module.receptionist.ReceptionistActor.getRuntimeBridgeSetting(ReceptionistActor.scala:96)
at improbable.module.receptionist.ReceptionistActor.improbable$module$receptionist$ReceptionistActor$$handleBridgeModuleRequest(ReceptionistActor.scala:83)
at improbable.module.receptionist.ReceptionistActor$$anonfun$1.applyOrElse(ReceptionistActor.scala:51)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:36)
at improbable.core.util.common.AnonymousFunctionSubscription.forward(AnonymousFunctionSubscription.scala:43)
at improbable.core.util.common.AnonymousFunctionSubscription.forwardToSubscriptions(AnonymousFunctionSubscription.scala:33)
at improbable.core.actor.extensible.ExtensibleActor$$anonfun$receive$1.applyOrElse(ExtensibleActor.scala:58)
at akka.actor.Actor$class.aroundReceive(Actor.scala:482)
at improbable.core.actor.extensible.ExtensibleActor.aroundReceive(ExtensibleActor.scala:12)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:526)
at akka.actor.ActorCell.invoke(ActorCell.scala:495)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
time=2017-06-20T22:22:09+01:00
ERRO [improbable.core.actor.extensible.ExtensibleActorReporter] SpatialOS-internal error: Exception in ReceptionistActor(Actor[akka://akkaFabric/user/moduleManager/ReceptionistModule1/Receptionist#2002765294]).
java.lang.RuntimeException: Unknown engine type UnityWorker
at improbable.module.receptionist.ReceptionistActor$$anonfun$2.apply(ReceptionistActor.scala:96)
at improbable.module.receptionist.ReceptionistActor$$anonfun$2.apply(ReceptionistActor.scala:96)
at scala.Option.getOrElse(Option.scala:121)
at improbable.module.receptionist.ReceptionistActor.getRuntimeBridgeSetting(ReceptionistActor.scala:96)
at improbable.module.receptionist.ReceptionistActor.improbable$module$receptionist$ReceptionistActor$$handleBridgeModuleRequest(ReceptionistActor.scala:83)
at improbable.module.receptionist.ReceptionistActor$$anonfun$1.applyOrElse(ReceptionistActor.scala:51)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:36)
at improbable.core.util.common.AnonymousFunctionSubscription.forward(AnonymousFunctionSubscription.scala:43)
at improbable.core.util.common.AnonymousFunctionSubscription.forwardToSubscriptions(AnonymousFunctionSubscription.scala:33)
at improbable.core.actor.extensible.ExtensibleActor$$anonfun$receive$1.applyOrElse(ExtensibleActor.scala:58)
at akka.actor.Actor$class.aroundReceive(Actor.scala:482)
at improbable.core.actor.extensible.ExtensibleActor.aroundReceive(ExtensibleActor.scala:12)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:526)
at akka.actor.ActorCell.invoke(ActorCell.scala:495)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
time=2017-06-20T22:22:09+01:00

Please note that I went and ran spatial clean and spatial codegen again just incase that was a problem.

Any ideas? I’d love to get this working :smile:

Update:
Played around with it some more. I realised that the console tools didn’t pick up any of the assemblies, so I built them anyway, which populated the list on startup and allowed me to connect with my Worker and then the client…
However there was some seriously funky stuff going on. When I spawned in with my known working player prefab, the Worker happily enabled a script on it which requires write access (the sendclientconnection script for heartbeat) and was vomiting errors until I manually turned it off.

Not sure if this is my problem or not! But if it’s a little tempremental like that I might need to jump back to building my players as per usual :frowning:


#16

Btw, if you need to have zero workers, you can try using a "singleton_worker" : {} configuration for an unmanaged worker; better yet, you should use the dynamic load balanced workers but set the "constant_config"'s "num_workers" : 0.


#17

Hm, last time I tried singleton_worker it complained saying I couldn’t use singleton for a Unityworker, only a custom one. But maybe it’ll work fine with zero workers, so I’ll need to try it out.
I’m still a bit cautious, I need to see this process running through the same as I’d expect building my workers and local hosting normally before I’m happy using it day to day. So far I’ve seen some unexpected behaviour :cry:


#18

Had to do kaffo’s step to fix the load balancer but things are working out fine.


#19

Hey @AtomiC :slight_smile:
So when you ran the worker and client separately, everything worked as expected?
First time I ran it I got an error about both worker and client trying to access the same prefab, then when I got into the game I had some seriously weird stuff going down.

Just wondering if it’s my fault or not! :smile:


Physics issues in VR