2013年10月22日火曜日

Missing (Mono Script) and using compiled MonoBehavior class

"The associated script can not be loaded."


That's the error I gotin Inspector when I clicked a GameObject in Hierarchy.


Where is my script, you ask.

It is in a standalone .NET assembly (.DLL). Any ordinary .NET DLLs can be used to contain plain vanilla C# classes that inherit MonoBehavior, and assigned to objects in Unity editor! (I like this arrangement so that I don't end up having too many script files in Unity environment.)

To understand this problem, I changed the editor's serialization from "Mixed" to "Force Text".
Why? Because files such as "myscene.unity" are binary format and it was hard to understand what was going on (you could still search binary file but it wasn't still clear how an Unity project is organized)

Here is the settings:



Anyhow, the reason why I got this error is because the GUID generated and stored in the DLL's meta file is different on each developer's machine, and I did not check in neither DLL nor its meta file. So Unity editor wasn't able to resolve the reference.

I opened myscene.unity and found a corresponding script reference.

--- !u!114 &1185866689
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 1185866687}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 1348454995, guid: 50b34ac0c06dffa4686286b9fc4fe8c1, type: 3}
  m_Name: 
  m_EditorClassIdentifier: 
  Address: localhost
  Port: 8080

Now, open a .meta file of the DLL that hosts the script class (MonoBehavior derived class)

fileFormatVersion: 2
guid: 50b34ac0c06dffa4686286b9fc4fe8c1
MonoAssemblyImporter:
  serializedVersion: 1
  iconMap: {}
  executionOrder: {}
  userData: 

The GUID values are the same. This is good. This is how Unity editor resolves script references.

I was getting an error because I did not check-in my DLLs but instead, expected each developer to build a DLL on his/her own machine. This caused a different GUID generated and stored in a .meta file, making it impossible for Unity editor to do the "wiring up".

2013年9月27日金曜日


UnityDLLExample


You can download the project source from GitHub

Demonstrate the basic setup of .NET DLL (managed assembly) project to be used within Unity project.

This is based on an Unity Forum article "How to build and debug external DLLs" written by someone else. 


Build a DLL


1) Create an empty Visual Studio solution (.sln) or use an existing solution.
2) Opton the solution
3) File -> Add -> Existing project...
4) Select UnityDLLExample.csproj
5) Build the solution/project
6) The output DLL, UnityDLLExample.dll, will appear in Assets/Plugins folder.


Copy the DLL to your Unity project


1) Create a new Unity project or use an existing one
2) Make sure that there is a folder called "Plugins" under "Assets" folder. If doesn't exist, create one.
3) Copy UnityDLLExample.dll to Assets/Plugins

Use "spin" script, that is compiled into UnityDLLExample.dll

using UnityEngine;
using System.Collections;

public class spin : MonoBehaviour {

// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
this.transform.Rotate(new Vector3(0, 0.2f, 0));
}
}

1) Open the Unity project in the Unity
2) Under Project window, navigate to Assets/Plugins folder
3) You should see an icon of UnityDLLExample.dll, with a small "Right arrow" next to it.
4) Click the "Right arrow" to expand the DLL to show the content. You should see a script called "spin"
5) You can drag this script to any object, such as Cube, to make it spin.


2012年7月17日火曜日

Delete Dropbox cached data to free up more disk space

I started receiving "Low Disk Space" warning from Windows.


My local hard disk has 500GB space. My typical disk space footprint has been somewhere between 200GB and 300GB. I hardly run out of disk space. So who is chewing up on my disk space?

I used free disk usage viewer called WinDirStat to find out which folders are taking up the most space. (I used a number of disk usage tools, free and commercial, and WinDirStat is my choice.)


Right off the bat, I saw several unusually large folders. 

These folders were under Dropbox's hidden cache folder. The folder contained many .log file (for testing our game, I use Dropbox folder to run the server. I delete .log files periodically, but Dropbox keep these files in the cache in case I ask for "recovering" deleted files.)

I deleted these large folders using WinDirStat. It has a menu to delete files/folders, so you don't have to use Windows Explorer.


Now, I freed up about 300GB of disk space, and I no longer receive the error message.


To delete Dropbox cache folder, you can also follow Dropbox's article on how to clear cache.

2012年2月14日火曜日

You could almost use DropBox to serve ClickOnce application. Almost...

My attempt to use DropBox public folder as a storage for our ClickOnce application files failed. It was close though. It would have been a great way to reduce the traffic to our sever: all requests to download over-300MB game files would go to DropBox instead of to our server.

ClickOnce application is represented by a file with .application extension. Usually, you put that file, along with setup.exe and all other files that are part of your ClickOnce application package on a server (most likely IIS if you are on Microsoft platform).

All I did was to copy the set of these package files to my DropBox public folder, and then get public URL pointing to the .application file. Then put a link with this URL on our game website, hoping that ClickOnce installation process would work just like when you have the package on your IIS box. If this worked, it would
greatly reduce the load to our server by off-loading it to DropBox's far superior network infrastructure.

Result: It worked for IE but not for other popular browser like Chrome, FireFox, and Opera.

IE took the .application and understood what to do and kicked off a ClickOnce installation process. Good start! However, with Chrome and FireFox, all I got was a plain XML content of the .application file in the browser window and nothing else.

This behavior holds even if you have Chrome Extension for ClickOnce and FireFox add-on for ClickOnce. They just don't know what to do with .application file.

Using Chrome's F12 Debugging tool, I saw the MIME type of the .application file, when accessed via DropBox public URL, has "text/plain" as MIME type.

That is the problem!

If you google "ClickOnce not working", you typically get two answers:

1. If not IE, install extension/add-on
2. Make sure that MIME type for .application extension is set to application/x-ms-application on your web server (e.g. IIS)

Unfortunately, with DropBox, there is no way to specify MIME type. I wish there were settings that allows me to specify MIME types as I own the contents stored in my DropBox folder (and I am a paid user...).

Anyway, this great idea of using DropBox public folder to host ClickOnce application is not feasible.

One question remains though... how come IE worked? Well, obviously Microsoft wants users to have better experience with their products/schemes, so IE must have treated .application extension specially. I kind of wish they rather not: because IE worked, it took me a while to realize it was MIME issue. If all client agents were presenting the same problem, it would have pointed to the direction of the server.

2012年1月13日金曜日

Registry tweaks for attaching to IIS 7 worker process from Visual Studio 2010

One of the common techniques for debugging ASP.NET applications is to attach the Visual Studio IDE debugger to a running IIS 7 worker process.

I have been doing this for so many years that my fingers, without even thinking, press "Ctrl+Alt+p" to bring up the "Attach to Process" dialog (alternatively, you can get to it by "Debug | Attach to process..." menu item)

With Visual Studio 2010 on Windows 7 (IIS7), when you bring up "Attach to Process", it will look like this by default:


Notice that "Show processes from all users" and "Show processes in all sessions" are not selected. That prevents IIS 7 worker process from showing up in the list. (By the way, my finger also remembers to hit "w" to jump to the parts for processes with their names starting with "w")

When you check both "Show processes from all users" and "Show processes in all sessions", w3wp.exe, our IIS 7 worker process, will show up.


However, it is a pain in the neck to press these two check boxes every time I want to debug.

I looked for configuration settings in "Tools | Options" dialog but could not find any. Then, I looked for Registry entries and I found them.

Here is the steps for making these check boxes permanently "checked".

1. Close all of your running instances of Visual Studio. This is important as in-memory values of a running instance will overwrite any new value in the registry. So, close it.

2. Go to HKCU\Software\Micorosoft\VisualStudio\10.0\Debugger, then look for a value called ShowProcessesFromAllSessions and ShowSystemProcesses

3. Change its value from 0 to 1 for both.

Below is to help you find it visually.


4. Now, start Visual Studio and see the effect. Back to "Ctrl+Alt+p" and then "w" workflow!

Enjoy!

2012年1月3日火曜日

IIS says "The process cannot access the file because it is being used by another process"

After I installed a SSL certificate to our IIS 7, any attempt to "Start" the website would result in this error message.

The process cannot access the file because it is being used by another process

I was like... "huh??"

It turned out that our VisualSVN server (a Windows Service process) was the issue. It was using port 443 which was preventing SSL-enabled website to start up.

I used CurrPorts to figure out who was using port 443.
Specify the following lines in its "filter" and you will see a list of processes, with their PID, who have opened port 80 and port 443

include:local:tcp:80
include:local:tcp:443

With the PID value and Task Manager, I found out that VisualSVN server was opening 443, but it was using a different IP address than the one I am using for our game website. What gives?

#1 Change VisualSVN Server Binding to a single IP address
I started VisualSVN GUI and clicked "Configure authentication options..." and in its "Network" tab, I found that "Server Binding" was set to "All IP addresses". That doesn't look right, and there is an alternative option called "These IP addresses", which takes a list of IP addresses, so I switched to it and gave the IP address it was bound to.

Now, I thought it would fix the website start up problem... but No! I was still getting the same "The process cannot access the file because it is being used by another process" error message.

It was clear to me that VisualSVN server process was causing the problem because if I stop the service then I can start the SSL-enabled website without any issue. The problem is that once I start the website, then I wan't able to start VisualSVN.

To fix the problem, I did a couple of things described below. I think the action #3 is actually attributed to fixing the problem, but #2 may also be part of it. I don't feel like undoing neither to just to find out that. I already spent hours on this and don't want break anything...

#2 Configure ListenOnlyList Windows registry subkey properly
This is one of the things described in a Microsoft KB to fix this "The process cannot access..." problem to fix the problem IIS website start up problem.The KB article had basic instructions but didn't have good example, so I ended up using an example from  this short article.

I checked the registry, and our machine did not have ListenOnlyList registry subkey. According to KB article, absence of ListenOnlyList subkey implies that it would operate on "any IP addresses". I am not quite sure what this really implies to my situation, but I wanted to eliminate any "blanket" rules like that, so I created ListenOnlyList subkey and added all of the IPs that this box was using.

#3 Change the port VisualSVN uses to something other than 443
The previous step did not fix the problem. I was still getting the same error message. Then next logical step is to not to use the same port. Luckly, SVN was for private use only and all we wanted was its transmissions were encrypted, so I could use any port. So, I chose another port value for SVN in VisualSVN GUI.

Of course, this causes all existing sandboxes on developers' machines to be invalid. To fix this, all we had to do was to invoke "relocate..." command on developers' machine to map to the new address of the SVN repository, which now needs an explicit port number as part of its URL.

After this, both website and VisualSVN server were running, providing the intended functionality.

2011年12月1日木曜日

Does the use of implicit event listener delegate for subscribing and unsubscribing to and from .NET event work?

I am pretty sure this has been discussed somewhere already on the net, but it was hard to find the answer and testing it myself was faster so here it is.

The answer is: YES, it would still correctly unsubscribe from the event.
If you run it, you will see only one line in the console output.

To demonstrate a leak, remove using() statement. You should see a second console output, indicative of an instance o Driver still hanging around in memory.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace EventTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Car c = new Car();


            using (Driver d = new Driver(c))
            {
                d.DoWork();
            }


            GC.Collect();
            GC.WaitForPendingFinalizers();


            c.Start();
        }
    }


    public class Driver : IDisposable
    {
        Car _car;
        public Driver(Car car)
        {
            _car = car;
            //_car.Started += new EventHandler(car_Started);
            _car.Started += car_Started;
        }


        public void DoWork()
        {
            _car.Start();
        }


        void car_Started(object sender, EventArgs e)
        {
            Console.WriteLine("Car has started");
        }


        public void Dispose()
        {
            //_car.Started -= new EventHandler(car_Started);
            _car.Started -= car_Started;
        }
    }


    public class Car
    {
        public event EventHandler Started;


        public void Start()
        {
            if (Started != null)
            {
                Started(this, EventArgs.Empty);
            }
        }
    }
}