How can I best automate ScriptableObject injection with Zenject? - zenject

I want users to be able to create my ScriptableObject assets outside of play mode so they can bind them to their objects in the scene. Since I don't want Zenject creating the ScriptableObjects, a Factory solution is not what I'm looking for. Therefore, I must somehow get the instance of these script objects to the Zenject installer and use 'QueueForInject'. Here are the two approaches I've found:
A) Manually adding these script objects to the Installer via the Inspector Window. Example:
public class GameInstaller : MonoInstaller<GameInstaller>
{
// Visible in the inspector window so user can add Script Objects to List
public List<ScriptableObject> myScriptObjectsToInject;
public override void InstallBindings()
{
foreach (ScriptableObject scriptObject in myScriptObjectsToInject)
{
Container.QueueForInject(scriptObject);
}
}
}
B) Use Unity's AssetDatabase and Resources APIs to find all script object instances then iterate through the list to QueueForInject. Example
// search for all ScriptableObject that reside in the 'Resources' folder
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject");
foreach (string guid in guids)
{
// retrieve the path string to the asset on disk relative to the Unity project folder
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
// retrieve the type of the asset (i.e. the name of the class, which is whatever derives from ScriptableObject)
System.Type myType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
//Find the relative path of the asset.
string resourcesDirectoryName = $"/Resources/";
int indexOfLastResourceDirectory = assetPath.LastIndexOf(resourcesDirectoryName) + resourcesDirectoryName.Length;
int indexOfExtensionPeriod = assetPath.LastIndexOf(".");
string assetPathRelative = assetPath.Substring(indexOfLastResourceDirectory, indexOfExtensionPeriod - indexOfLastResourceDirectory);
//Grab the instance of the ScriptableObject.
ScriptableObject scriptObject = Resources.Load(assetPathRelative, myType) as ScriptableObject;
if (scriptObject == null)
{
Debug.LogWarning(
"ScriptableObject asset found, but it is not in a 'Resources' folder. Current folder = " +
assetPath);
continue;
}
else
{
Container.QueueForInject(scriptObject);
}
}
With option A) the end user must remember to manually place the script object in the list for every new script object that they create. I'd rather find an automated way so the user doesn't have to know/remember this extra manual process.
With option B) I get an automated solution which, is great. But the end user must remember to store each ScriptableObject asset file in a directory called "Resources" because it's the only way the Resources.Load API will find the instance. I can warn the user if not found, which is nice. But they still are forced to comply to remove the warnings.
I can live with option B if I must, but I'd really like to take it a step further and make it completely invisible to the end user. Has anyone come up with a craftier automated solution for ScriptObjects that exist outside of Play Mode?

Related

Visualization of dynamically created modules

I am building a dynamic multi agent simulation in OMNeT and for this I have to create new modules at runtime. The module creation is working, however, the modules created at runtime are not appearing in the 3D visualization.
module "node" is created sucessfully
Does anyone know how to make the module appear in the visualization? Do I have to update the visualization module?
omnet.ini:
[General]
network = AgentNetwork
*.visualizer.osgVisualizer.typename = "IntegratedOsgVisualizer"
*.visualizer.*.mobilityVisualizer.animationSpeed = 1
*.visualizer.osgVisualizer.sceneVisualizer.typename = "SceneOsgEarthVisualizer"
*.visualizer.osgVisualizer.sceneVisualizer.mapFile = "hamburg.earth"
AgentSpawner:
void AgentSpawner::initialize()
{
cMessage *timer = new cMessage("timer");
scheduleAt(1.0, timer);
}
void AgentSpawner::handleMessage(cMessage *msg)
{
cModuleType *moduleType = cModuleType::get("simulations.Agent");
cModule *module = moduleType->create("node", getParentModule());
// set up parameters and gate sizes before we set up its submodules
module->par("osgModel") = "3d/glider.osgb.(20).scale.0,0,180.rot";
module->getDisplayString().parse("p=200,100;i=misc/aircraft");
module->finalizeParameters();
// create internals, and schedule it
module->buildInside();
module->callInitialize();
module->scheduleStart(simTime()+5.0);
}
The OSG visualization info is maintained totally separately from the actual simulation model module object (that's because the visualization must be ALWAYS optional in the simulation, so make sure your simulation builds fine with OSG totally turned off). This means that an entirely different data structure is built during initialization time from the existing network nodes. As this is done only once during the initialization, dynamically created modules will not have their visualization counterpart data structure.
The code which created the corresponding objects is here.
The solution would be to look up the NetworkNodeOsgVisualizer module in your AgentSpawner code then create and add the corresponding data structures (NetworkNodeOsgVisualization objects). The needed methods (create and add) are there, but sadly they are protected, so you many need to modify the INET code and make them public to be able to call them.

Sitecore: Glass Mapper Code First

It is possible to automatically generate Sitecore templates just coding models? I'm using Sitecore 8.0 and I saw Glass Mapper Code First approach but I cant find more information about that.
Not sure why there isn't much info about it, but you can definitely model/code first!. I do it alot using the attribute configuration approach like so:
[SitecoreType(true, "{generated guid}")]
public class ExampleModel
{
[SitecoreField("{generated guid}", SitecoreFieldType.SingleLineText)]
public virtual string Title { get; set; }
}
Now how this works. The SitecoreType 'true' value for the first parameter indicates it may be used for codefirst. There is a GlassCodeFirstDataprovider which has an Initialize method, executed in Sitecore's Initialize pipeline. This method will collect all configurations marked for codefirst and create it in the sql dataprovider. The sections and fields are stored in memory. It also takes inheritance into account (base templates).
I think you first need to uncomment some code in the GlassMapperScCustom class you get when you install the project via Nuget. The PostLoad method contains the few lines that execute the Initialize method of each CodeFirstDataprovider.
var dbs = global::Sitecore.Configuration.Factory.GetDatabases();
foreach (var db in dbs)
{
var provider = db.GetDataProviders().FirstOrDefault(x => x is GlassDataProvider) as GlassDataProvider;
if (provider != null)
{
using (new SecurityDisabler())
{
provider.Initialise(db);
}
}
}
Furthermore I would advise to use code first on development only. You can create packages or serialize the templates as usual and deploy them to other environment so you dont need the dataprovider (and potential risks) there.
You can. But it's not going to be Glass related.
Code first is exactly what Sitecore.PathFinder is looking to achieve. There's not a lot of info publicly available on this yet however.
Get started here: https://github.com/JakobChristensen/Sitecore.Pathfinder

Adding to a list from another class

I am trying to add the instance of an object that I click on to a list on my control object. However when I do so it says that the reference is not set to an instance of an object. The code I have to instantiate the list on the control object is:
public List<Transform> selected = new List<Transform>();
And I tried to add to it to that list using this code attached to the unit:
if (!selected)
{
// Set selected state
selected = true;
// Add to Selected List
control.GetComponent<ForwardCommandScript>().selected.Add(this.transform);
// Set material colour brighter
oldColour = gameObject.renderer.material.color;
newColour = oldColour + new Color(0.2f, 0.2f, 0.2f);
gameObject.renderer.material.color = newColour;
}
I have tried with transform as well. Later I will try to remove it by finding a reference id that was set when the unit is instantiated so should I try to add the script instead of the object if I need to find its variables and then delete the game object attached to the script. I have tried with the GameObject, transform and the class. I wanted to use the class so I can easily access the variables. I have posted this on unity answers and forums but no one replied in the week it was up and I don't like reposting the same stuff on the same site.
Cheers, Scobbo
Your error NullReferenceException: Object reference not set to an instance of an object states that something in the associated line is null. Since the error message doesn't state which part is null, you have to split your code up and check which part is failing.
I'm not sure how you split it up, but try it this way:
var script = control.GetComponent<ForwardCommandScript>();
if (script == null) Debug.Log("script not found");
if (script.selected == null) Debug.Log("selected is null");
script.selected.Add(this.transform);
Now you should get one of the two messages in your debug log before the exception raises. Either the script was not found and you have to check if it is correctly assigned to the game object and if control is the correct game object, or selected is null which should not happen if you initialized it like you posted...
Thanks for adding the complete Error Message :)
you need to replace
control.GetComponent<ForwardCommandScript>().selected.Add(this.transform);
with
control.GetComponent<ForwardCommandScript>().selected.Add(transform);
because
this
is a reference to the script and not the GameObject. you could also use gameObject.transform which transform is just an abbreviation for

loading classes with jodd and using them in drools

I am working on a system that uses drools to evaluate certain objects. However, these objects can be of classes that are loaded at runtime using jodd. I am able to load a file fine using the following function:
public static void loadClassFile(File file) {
try {
// use Jodd ClassLoaderUtil to load class into the current ClassLoader
ClassLoaderUtil.defineClass(getBytesFromFile(file));
} catch (IOException e) {
exceptionLog(LOG_ERROR, getInstance(), e);
}
}
Now lets say I have created a class called Tire and loaded it using the function above. Is there a way I can use the Tire class in my rule file:
rule "Tire Operational"
when
$t: Tire(pressure == 30)
then
end
Right now if i try to add this rule i get an error saying unable to resolve ObjectType Tire. My assumption would be that I would somehow need to import Tire in the rule, but I'm not really sure how to do that.
Haven't use Drools since version 3, but will try to help anyway. When you load class this way (dynamically, in the run-time, no matter if you use e.g. Class.forName() or Jodd), loaded class name is simply not available to be explicitly used in the code. I believe we can simplify your problem with the following sudo-code, where you first load a class and then try to use its name:
defineClass('Tire.class');
Tire tire = new Tire();
This obviously doesn't work since Tire type is not available at compile time: compiler does not know what type you gonna load during the execution.
What would work is to have Tire implementing some interface (e.g. VehiclePart). So then you could use the following sudo-code:
Class tireClass = defineClass('Tire.class');
VehiclePart tire = tireClass.newInstance();
System.out.println(tire.getPartName()); // prints 'tire' for example
Then maybe you can build your Drools rules over the interface VehiclePart and getPartName() property.
Addendum
Above make sense only when interface covers all the properties of dynamically loaded class. In most cases, this is not a valid solution: dynamically loaded classes simply do not share properties. So, here is another approach.
Instead of using explicit class loading, this problem can be solved by 'extending' the classloader class path. Be warn, this is a hack!
In Jodd, there is method: ClassLoaderUtil.addFileToClassPath() that can add a file or a path to the classloader in the runtime. So here are the steps that worked for me:
1) Put all dynamically created classes into some root folder, with the respect of their packages. For example, lets say we want to use a jodd.samples.TestBean class, that has two properties: number (int) and a value (string). We then need to put it this class into the root/jodd/samples folder.
2) After building all dynamic classes, extend the classloaders path:
ClassLoaderUtil.addFileToClassPath("root", ClassLoader.getSystemClassLoader());
3) load class and create it before creating KnowledgeBuilder:
Class testBeanClass = Class.forName("jodd.samples.TestBean");
Object testBean = testBeanClass.newInstance();
4) At this point you can use BeanUtils (from Jodd, for example:) to manipulate properties of the testBean instance
5) Create Drools stuff and add insert testBean into session:
knowledgeSession.insert(testBean);
6) Use it in rule file:
import jodd.samples.TestBean;
rule "xxx"
when
$t: TestBean(number == 173)
then
System.out.println("!!!");
end
This worked for me. Note that on step #2 you can try using different classloader, but you might need it to pass it to the KnowledgeBuilderFactory via KnowledgeBuilderConfiguration (i.e. PackageBuilderConfiguration).
Another solution
Another solution is to simply copy all object properties to a map, and deal with the map in the rules files. So you can use something like this at step #4:
Map map = new HashMap();
BeanTool.copy(testBean, map);
and later (step #5) add a map to Drools context instead of the bean instance. In this case it would be even better to use defineClass() method to explicitly define each class.

Creating a new instance from within CFC

Now this would seem to be something very straight forward, but seemingly not so in ColdFusion. I need to create an instance of a CFC from within itself as in var a = new this() but this obviously does not work. The CFC name can't be used as it is a base that will be extended so I am attempting a hack around the issue with the following:
component {
public function subQuery (required string table) {
var classPath = getMetaData(this).fullname;
return createObject("component", classPath).init(table, this.dsn);
}
}
This would be acceptable but the class path returned from getMetaData(this).fullname is incorrect. The CFC is within a folder named with a hypen as in my-folder and the returned path looks like my.-folder.myCFC with a period inserted before the hyphen. Obviously I could manipulate this string with a Regex but that is just not a road I want to go down.
Hoping someone has a cleaner approach, thanks.
You should be able to do it without any context on the object name in theory, as it will be being executed from within itself and it should check its current directory.
The following should therefore do the job you need
var classPath = ListLast(getMetaData(this).fullname,'.');
return createObject("component", classPath).init(table, this.dsn);
This way it doesn't matter what the directory names are, and it will work on any objects that extend that one regardless of directory structure, or for a complete example
public function cloneMe() {
return CreateObject('component', ListLast(getMetaData(this).fullname,'.')).init(argumentCollection=arguments);
}
This way any arguments passed in will be passed through into the init. I.e. an extending CFC may redefine the method as the following (if you want errors when the init arguments aren't supplied)
public function cloneMe(required string table) {
return super.cloneMe(table=arguments.table,dsn=this.dsn);
}