package hrt.prefab; /** Prefab is an data-oriented tree container capable of creating instances of Heaps objects. **/ @:keepSub class Prefab { /** The type of prefab, allows to identify which class it should be loaded with. **/ public var type(default, null) : String; /** The name of the prefab in the tree view **/ public var name(default, set) : String; /** The parent of the prefab in the tree view **/ public var parent(default, set) : Prefab; /** The associated source file (an image, a 3D model, etc.) if the prefab type needs it. **/ public var source(default, set) : String; /** The list of children prefab in the tree view **/ public var children(default, null) : Array; /** Tells if the prefab will create an instance when calling make() or be ignored. Also apply to this prefab children. **/ public var enabled : Bool = true; /** A storage for some extra properties **/ public var props : Any; /** Creates a new prefab with the given parent. **/ public function new(?parent) { this.parent = parent; children = []; } function set_name(n) { return name = n; } function set_source(f) { return source = f; } function set_parent(p) { if( parent != null ) parent.children.remove(this); parent = p; if( parent != null ) parent.children.push(this); return p; } #if editor /** Allows to customize how the prefab object is edited within Hide **/ public function edit( ctx : hide.prefab.EditContext ) { } /** Allows to customize how the prefab object is displayed / handled within Hide **/ public function getHideProps() : hide.prefab.HideProps { return { icon : "question-circle", name : "Unknown" }; } /** Allows to customize how the prefab instance changes when selected/unselected within Hide **/ public function setSelected( ctx : Context, b : Bool ) { var materials = ctx.shared.getMaterials(this); if( !b ) { for( m in materials ) { m.mainPass.stencil = null; m.removePass(m.getPass("highlight")); } return; } var shader = new h3d.shader.FixedColor(0xffffff); for( m in materials ) { var p = m.allocPass("highlight"); p.culling = None; p.depthWrite = false; p.addShader(shader); } } #end /** Iterate over children prefab **/ public inline function iterator() : Iterator { return children.iterator(); } /** Override to implement your custom prefab data loading **/ function load( v : Dynamic ) { throw "Not implemented"; } /** Override to implement your custom prefab data saving **/ function save() : {} { throw "Not implemented"; return null; } /** Creates an instance for this prefab only (and not its children). Use make(ctx) to creates the whole instances tree; **/ public function makeInstance( ctx : Context ) : Context { return ctx; } /** Allows to customize how an instance gets updated when a property name changes. You can also call updateInstance(ctx) in order to force whole instance synchronization against current prefab data. **/ public function updateInstance( ctx : Context, ?propName : String ) { } /** Removes the created instance for this prefab only (not is children). If false is returned, the instance could not be removed and the whole context scene needs to be rebuilt **/ public function removeInstance( ctx : Context ) : Bool { return false; } /** Save the whole prefab data and its children. **/ @:final public function saveData() : {} { var obj : Dynamic = save(); obj.type = type; if( !enabled ) obj.enabled = false; if( name != null ) obj.name = name; if( source != null ) obj.source = source; if( children.length > 0 ) obj.children = [for( s in children ) s.saveData()]; if( props != null && obj.props == null ) obj.props = props; return obj; } /** Load the whole prefab data and creates its children. **/ @:final public function loadData( v : Dynamic ) { type = v.type; name = v.name; enabled = v.enabled == null ? true : v.enabled; props = v.props; source = v.source; load(v); if( children.length > 0 ) children = []; var children : Array = v.children; if( children != null ) for( v in children ) loadPrefab(v, this); } /** Updates in-place the whole prefab data and its children. **/ public function reload( p : Dynamic ) { name = p.name; enabled = p.enabled == null ? true : p.enabled; props = p.props; source = p.source; load(p); var childData : Array = p.children; if( childData == null ) { if( this.children.length > 0 ) this.children = []; return; } var curChild = new Map(); for( c in children ) curChild.set(c.name, c); var newchild = []; for( v in childData ) { var name : String = v.name; var prev = curChild.get(name); if( prev != null && prev.type == v.type ) { curChild.remove(name); prev.reload(v); newchild.push(prev); } else { newchild.push(loadPrefab(v,this)); } } children = newchild; } /** Creates the correct prefab based on v.type and load its data and children. If one the prefab in the tree is not registered, a hxd.prefab.Unkown is created instead. **/ public static function loadPrefab( v : Dynamic, ?parent : Prefab ) { var pcl = @:privateAccess Library.registeredElements.get(v.type); var pcl = pcl == null ? null : pcl.cl; if( pcl == null ) pcl = Unknown; var p = Type.createInstance(pcl, [parent]); p.loadData(v); return p; } /** Creates an instance for this prefab and its children. **/ public function make( ctx : Context ) : Context { if( !enabled ) return ctx; if( ctx == null ) { ctx = new Context(); ctx.init(); } ctx = makeInstance(ctx); for( c in children ) c.make(ctx); return ctx; } #if castle /** Returns which CDB model this prefab props represents **/ public function getCdbModel( ?p : Prefab ) : cdb.Sheet { if( p == null ) p = this; if( parent != null ) return parent.getCdbModel(p); return null; } #end /** Search the prefab tree for the prefab matching the given name, returns null if not found **/ public function getPrefabByName( name : String ) { if( this.name == name ) return this; for( c in children ) { var p = c.getPrefabByName(name); if( p != null ) return p; } return null; } /** Simlar to get() but returns null if not found. **/ public function getOpt( cl : Class, ?name : String ) : T { if( name == null || this.name == name ) { var cval = to(cl); if( cval != null ) return cval; } for( c in children ) { var p = c.getOpt(cl, name); if( p != null ) return p; } return null; } /** Search the prefab tree for the prefab matching the given prefab class (and name, if specified). Throw an exception if not found. Uses getOpt() to return null instead. **/ public function get( cl : Class, ?name : String ) : T { var v = getOpt(cl, name); if( v == null ) throw "Missing prefab " + (name == null ? Type.getClassName(cl) : (cl == null ? name : name+"(" + Type.getClassName(cl) + ")")); return v; } /** Return all prefabs in the tree matching the given prefab class. **/ public function getAll( cl : Class, ?arr: Array ) : Array { return findAll(function(p) return p.to(cl)); } /** Find a single prefab in the tree by calling `f` on each and returning the first not-null value returned, or null if not found. **/ public function find( f : Prefab -> Null ) : Null { var v = f(this); if( v != null ) return v; for( p in children ) { var v = p.find(f); if( v != null ) return v; } return null; } /** Find several prefabs in the tree by calling `f` on each and returning all the not-null values returned. **/ public function findAll( f : Prefab -> Null, ?arr : Array ) : Array { if( arr == null ) arr = []; var v = f(this); if( v != null ) arr.push(v); for( o in children ) o.findAll(f,arr); return arr; } /** Returns all prefabs in the tree matching the specified class. **/ public function flatten( ?cl : Class, ?arr: Array ) : Array { if(arr == null) arr = []; if( cl == null ) arr.push(cast this); else { var i = to(cl); if(i != null) arr.push(i); } for(c in children) c.flatten(cl, arr); return arr; } /** Returns the first parent in the tree matching the specified class or null if not found. **/ public function getParent( c : Class ) : Null { var p = parent; while(p != null) { var inst = p.to(c); if(inst != null) return inst; p = p.parent; } return null; } /** Converts the prefab to another prefab class. Returns null if not of this type. **/ public function to( c : Class ) : Null { return Std.instance(this, c); } /** Returns the absolute name path for this prefab **/ public function getAbsPath() { var p = this; var path = []; while(p.parent != null) { var n = p.name; if( n == null ) n = getDefaultName(); path.unshift(n); p = p.parent; } return path.join('.'); } /** Returns the default name for this prefab **/ public function getDefaultName() : String { if(source != null) { var f = new haxe.io.Path(source).file; f = f.split(" ")[0].split("-")[0]; return f; } return type.split(".").pop(); } /** Clone this prefab and all its children **/ public function clone() : Prefab { var obj = saveData(); return loadPrefab(haxe.Json.parse(haxe.Json.stringify(obj))); } }