what can we do with mxs arrays? (SDK)


#1

there is a weak link in many my scripts. it’s a using of arrays. it leaks badly.

what could replace the mxs array and don’t leak in memory? any ideas?

actually i’m thinking about writing a custom class which will not leak.


#2

my guess is it’s not the array as such that leaks but the value class it inherits from and the handle it uses to store the data, so if your own array class is going to work like the existing mxs class and use an array of value pointers so it can store any mxs type then it will still leak. The alternative is dedicated array classes for floats/integers/references/point3/matrix3 etc which then become a nightmare to maintain but might not leak as much. As for writing your own classes then the BigMatrix Value in the mxsagni project is a good place to start (ExtClass.cpp & ExtClass.h), again it looks like a nightmare of getting your includes in the correct order!


#3

If I had a better knowledge of the SDK I would try to help, but I can see what Klvnk said about having dedicated arrays for each class being a nightmare to maintain.

Just out of curiosity, do you have a short sample code to test?


#4

the idea with custom class works… here is a simple template for FloatTab class:

/******************************************* HEADER (.h) *********************************************/

applyable_class_debug_ok (FloatTabValue)

class FloatTabValue : public Value
{
private: 
	Value* _parameter;
public:
	Tab<float> tab;

	FloatTabValue();
	FloatTabValue(Tab<float> t);
	FloatTabValue(int count);
	
	ValueMetaClass* local_base_class() { return class_tag(FloatTabValue); } // local base class in this class's plug-in
	classof_methods(FloatTabValue, Value);

	Array* AsArray()
	{
		Array* arr = new Array(0);
		for (int k=0; k < tab.Count(); k++) arr->append(Float::intern(tab[k]));
		return arr;
	}

	void		collect() { delete this; }
	void		sprin1(CharStream* s);
	void		gc_trace();
#	define		is_floattab(v) ((DbgVerify(!is_sourcepositionwrapper(v)), (v))->tag == class_tag(FloatTabValue))
	Tab<float>	to_floattab() { return tab; }

	Value* operator[](const int i) const { return Float::intern(tab[i]); }

	def_prop_getter (count);

	def_generic	(get,			"get");
	def_generic	(put,			"put");
};

#endif //_FLT_TAB_MXS_CLASS


/******************************************* SOURCE (.cpp) *********************************************/

visible_class_instance (FloatTabValue, "FloatTab")
BOOL is_floatTabValue(Value* val)
{
	return (val->is_kind_of(class_tag(FloatTabValue))) ;
}

Value*
FloatTabValueClass::apply(Value** arg_list, int count, CallContext* cc)
{
	switch (count)
	{
		case 0: 
		{
			return new FloatTabValue();
		}
		case 1: 
		{
			Value* v0 = arg_list[0]->eval()->get_heap_ptr();

			if (is_array(v0)) 
			{
				Array* arr = (Array*)v0;

				Tab<float> tab;
				tab.SetCount(arr->size);
				for (int k=0; k < arr->size; k++) tab[k] = arr->data[k]->to_float();
				return new FloatTabValue(tab);
			}
			if (is_number(v0)) 
			{
				return new FloatTabValue(v0->to_int());
			}
			break;
		}
		default: 
			check_arg_count(FloatTabValue, 1, count);
			break;
	}
	return &undefined;
}

// -------------------- FloatTabValue methods -------------------------------- 

FloatTabValue::FloatTabValue()
{
	tag = class_tag(FloatTabValue);
}
FloatTabValue::FloatTabValue(Tab<float> tab)
{
	tag = class_tag(FloatTabValue);
	this->tab = tab;
}
FloatTabValue::FloatTabValue(int count)
{
	tag = class_tag(FloatTabValue);
	this->tab.SetCount(count, 1);
	for (int k=0; k < count; k++) this->tab[k] = 0.0f;
}

void
FloatTabValue::sprin1(CharStream* s)
{
	s->puts(_M("FloatTab["));
	Value* count = Integer::intern(this->tab.Count());
	Array* arr = this->AsArray();
	count->sprin1(s);
	s->puts(_M("] "));	
	arr->sprin1(s);
	//s->puts(_M(""));	
}

Value*
FloatTabValue::get_vf(Value** arg_list, int count)
{
	check_arg_count(get, 2, count + 1);
	Value* arg = arg_list[0];
	if (is_number(arg)) 
	{
		int	index = arg->to_int() - 1;
		if (index > -1 && index < this->tab.Count())
		{
			return Float::intern(this->tab[index]);
		}
	}
	return &undefined;  // array indexes are 1-based !!!
}
Value*
FloatTabValue::put_vf(Value** arg_list, int count)
{
	check_arg_count(get, 3, count + 1);
	if (is_number(arg_list[0]) && is_number(arg_list[1])) 
	{
		int index = arg_list[0]->to_int() - 1;
		float val = arg_list[1]->to_float();
		if (index > -1)
		{
			if (index >= this->tab.Count()) this->tab.SetCount(index+1);
			this->tab[index] = val;
			return &ok; //Float::intern(val);
		}
	}
	return &undefined;  // array indexes are 1-based !!!
}

void
FloatTabValue::gc_trace()
{
	Value::gc_trace();
}

Value*
FloatTabValue::get_count(Value** arg_list, int count)
{
	return Integer::intern((this->tab).Count());
}

it doesn’t leak at all!

a = floattab 1000000
for k=1 to a.count do a[k] = k
--time:562 memory:0L 

a = #()
a.count = 1000000
for k=1 to a.count do a[k] = k
--time:760 memory:55992776L

#5

nice, though I don’t like the built in sdk tab, IIRC it pre allocates to a 100 items and always seems a bit “awkward” and limited so I’ve taken to using stl with a custom max heap allocator.


#6

i will stay with sdk tabs. i need a solution right now for Point2(3,4) and float.


#7

you may want to add the following to your class

//in the class def
 
 def_prop_setter(count);
 use_generic(free, "free");
 
 // in the cpp
 
 Value* FloatTabValue::free_vf(Value** arg_list, int count)
 {
 	check_arg_count(free, 1, count + 1);
 	tab.ZeroCount(); 
 	return &ok;  
 }
 
 Value* FloatTabValue::set_count(Value** arg_list, int count)
 {
 	check_arg_count(get, 3, count + 1);
 	int newtabcount = arg_list[0]->to_int();
 	if (newtabcount < 0)
 		throw RuntimeError("Positive values only", arg_list[0]);
 	tab.SetCount(newtabcount);
 	return &ok;
 }

that way you can clean up memory without waiting for the floattab to go out of scope.


 a = floattab 1000000 
for k=1 to a.count do a[k] = k
 free a;

though using free on your standard array would probably cure your leak :wink:


#8

i was just experimenting with FloatTabValue… the goal is Point3TabValue.
for fast and leaking free point3 operations:

collect mesh verts positions (Point3TabValue(Mesh* mesh))

transform buffer

append if unique (with tolerance)

make unique (with tolerance)

search a value (with tolerance)

round (with tolerance)

sort by component

sort by distance to specified point

union

intersect

everything is almost done. it works very fast with zero memory use.


#9

I failed to find the correct includes to compile the above example so I have a question.

Can the above class be extended to have public methods? So it could be used in mxs like this:

ptTab = point3Tab()
ptTab.fromMeshVerts (snapshotAsMesh selection[1])
ptTab.Add [0,0,0]
avg = ptTab.Average()
minmax = ptTab.MinMax()

Any little example of a class with one property and a method would be just great.
I created a class visible to mxs using Klvnk’s kd3tree class with working property getters and setters, but how to define a single public method is still a problem for me.


#10

in my class it’s already implemented as:


pp = point3tab <mesh or node> local:bool transform:matrix3

-- or

pp = point3tab()
append pp <mesh>

append pp <point3 or (array of point3) or point3tab or mesh> start:int count:int

pp.center -- geometry center
pp.min -- minimum for all three compoments
pp.max -- maximum for all three compoments

pp.pivot -- average

coerce:
pp as mesh --(needs faces list as parameter)
pp as array -- (or pp.value)
pp as floattab
pp as bbox3
-- and more

#11

Ok. Guess I wasn’t clear enough.
As I understand, your example is still about class instance property getters/setters and not about a class instance methods.

And that’s the way I’d like my class to function… very similar to RayMeshgridIntersect

obj = MyClass()
obj.AddNode $teapot001
obj.SetGridSize 10 20 10
obj.GetNearest [10,20,30]
...

ps. I’ve seen that RayMeshgridIntersect is derived from FPMixinInterface, but is it possible to have methods accessible from mxs in a class that derive from Value?


#12

the only way to organize and publish class methods in mxs is to use the mixin interfaces, but calling these methods in the “simple” mxs syntax results in a loss of performance and memory:

for example:

delete objects
b = converttopoly (box())
gc()

-- 'plain' syntax:
(
	t0 = timestamp()
	h0 = heapfree

	for k=1 to 1000 do
	(
		b.selectHardEdges()
	)

	format "#1 time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)
-- the very well known trick:
(
	t0 = timestamp()
	h0 = heapfree

	selectHardEdges = b.selectHardEdges
	for k=1 to 1000 do
	(
		selectHardEdges()
	)

	format "#2 time:% heap:%\n" (timestamp() - t0) (h0 - heapfree)
)

that’s why I don’t like to use interfaces


#13

in SDK of course I have class methods for this mxs classes, and use them. but I don’t publish them for MXS.
if I wanted to publish them, I would have to add an interface to code that I absolutely do not need in relation to the SDK


#14

Thanks a lot.
Now it’s at least clear why I couldn’t make my methods exposed to mxs.


#15

Worth mentioning there’s no need for that trick as you can always use

Editable_Poly.selectHardEdges b

#16

I finally made it using the example by Rodrigue Cloutier provided in ancient Descreet Sparks chm.

sparks_archive.chm (3.3 MB) source

ps. Why there’s still no sticky thread for c++ faq ?


#17

Ok, I’m stuck again. :slight_smile:
Now my mixin interface functions are accessible from maxscript, but they return nothing back except OK no matter what return type is defined.
What I did wrong?

#pragma region includes

#include "Max.h"
#include "resource.h"
#include "istdplug.h"
#include "iparamb2.h"
#include "iparamm2.h"

#include <maxscript/maxscript.h>
#include <ifnpub.h>
#include "UniformGrid.h" // my custom implementation of uniform grid
#include <bitarray.h>

#pragma endregion includes

extern TCHAR *GetString(int id);

#define UGRID_CLASS_ID Class_ID(0x352333, 0x57664)
#define UGRID_INTERFACE_ID Interface_ID(0x352323, 0x55664)
#define GetUGridInterface(obj) ((IUGrid*)obj->GetInterface(UGRID_INTERFACE_ID)) 



extern FPInterfaceDesc ugrid_mixininterface;

enum { addPoint, addPoints, getCell }; 

interface IUGrid : public FPMixinInterface{

	BEGIN_FUNCTION_MAP 

		VFN_1( addPoint,  fnAddPoint,  TYPE_POINT3        );
		VFN_1( addPoints, fnAddPoints, TYPE_POINT3_TAB_BR );
		VFN_1( getCell,   fnGetCell,   TYPE_INT           );
		
	END_FUNCTION_MAP 

	
	FPInterfaceDesc* GetDesc() { return &ugrid_mixininterface; }

	virtual void fnAddPoint  ( Point3 pt        ) = 0;
	virtual void fnAddPoints ( Tab<Point3*> pts ) = 0;
	virtual int  fnGetCell   ( int index        ) = 0;

};


class UGrid : public ReferenceTarget, public IUGrid
{

public:

	UGrid(){ }

	void DeleteThis(){ delete this; }
	Class_ID ClassID(){ return UGRID_CLASS_ID; }
	SClass_ID SuperClassID(){ return REF_TARGET_CLASS_ID; }
	void GetClassName(TSTR& s) {s = GetString(IDS_CLASS_NAME);}
	int IsKeyable(){ return 0;}


	RefResult NotifyRefChanged( Interval i, RefTargetHandle rth, PartID& pi,RefMessage rm ) { return REF_SUCCEED; }
	
	BaseInterface* GetInterface(Interface_ID id){ if (id == UGRID_INTERFACE_ID) return (IUGrid*)this; else return ReferenceTarget::GetInterface(id); }


	UniformGrid ug;

	void fnAddPoint( Point3 pt ){ mprintf( _T(">>[%f,%f,%f]\n"), pt.x, pt.y, pt.z ); }
	
	void fnAddPoints( Tab<Point3*> pts ){

		ug = UniformGrid();
		mprintf( _T("Grid is created\n") );
		
		ug.SetSize( 11, 11);
		mprintf( _T("Grid size is set\n") );
		
		ug.BuildWithPoints( pts );
		mprintf( _T("Grid building complete...\n") );
	}

	int  fnGetCell( int index );
};


int UGrid::fnGetCell(int index)
{
	mprintf( _T("Trying to get cell verts indexes...\n") );
		
	BitArray* bits = new BitArray();
	bits->SetSize(555);
	bits->ClearAll();

	ug.GetNeighbors( index, bits );

	int cc = bits->NumberSet();
	mprintf( _T("Cell indexes count: %d\n"), cc );

	return 666;
}



class UGridClassDesc:public ClassDesc2 {

public:

	int IsPublic() { return TRUE; }

	void* Create(BOOL loading = FALSE) { return new UGrid(); }

	const TCHAR* ClassName() { return GetString(2); }

	SClass_ID SuperClassID() { return REF_TARGET_CLASS_ID; }

	Class_ID ClassID() { return UGRID_CLASS_ID; }

	const TCHAR* Category() { return _T(""); }

	const TCHAR* InternalName() { return _T("UGrid"); }

	HINSTANCE HInstance() { return hInstance; }



};

static UGridClassDesc UGridClassCD;
ClassDesc2* GetUGridtDesc() { return &UGridClassCD; }


static FPInterfaceDesc ugrid_mixininterface(

	UGRID_INTERFACE_ID, _T("foo"), 0, &UGridClassCD, FP_MIXIN, 

	addPoint,  _T("AddPoint"), 0, TYPE_VOID, 0, 1,
	_T("point"), 0, TYPE_POINT3,

	addPoints,  _T("AddPoints"), 0, TYPE_VOID, 0, 1,
	_T("points"), 0, TYPE_POINT3_TAB_BR,

	getCell,  _T("GetCell"), 0, TYPE_INT, 0, 1, _T("index"), 0, TYPE_INT,

	p_end 

);