[Closed] Shared struct: global or not?
Hi all!
I’m working in my spare time on several maxscript I’d like to integrate in our pipeline (we’re a small studio, but you know… : ).
I used to code during university years before turning to full time art and now I’m trying to remember how to do stuff properly.
My plan is to save useful functions in specific struct stored into separated ms files. I’m basically pretending to do object oriented programming and writing classes. (Well,sort of…)
Up to now the ms files end with the declaration of a global struct and they are evaluated at startup: this means that every other script I use doesn’t need to include them: I can directly use the struct and its functions.
So I don’t need to manually add stuff and there is only 1 instance for each class…
But they’re all global, and I know that is not the most elegant thing.
So I’m here looking for some good programming hint: is this be a nice solution or could be better to define only structs (aka classes) and then include them only where needed, creating a specific instance for each script?
I already knkw quite well game engines and shaders and basic coding, but I’m working hard on this stuff (and learning python too): I want to become better tech artist and in time improve the studio pipeline
Thanks for any suggestion!
Berna
struct TestStruct
(
a = #test,
fn printTest = (print a),
fn printAny v = (print v)
)
look at this example first, and try to call methods of this struct from both definition and instance.
as you can see the method printAny can be called directly from definition, and it doesn’t need an instance of it.
printTest needs an instance, because it uses members from definition.
so, if your structure definition is only set of commonly used methods it cat stay as a global definition
another option is to make only one instance of this struct, and use it in other places (that is what i usually do):
global TestStruct
(
struct TestStruct
(
a = #test,
fn printTest = (print a),
fn printAny v = (print v)
)
TestStruct = TestStruct()
ok
)
but if you need a unique instance of this struct for different tools you can create it from definition . so you have to keep own definition in itself.
global TestStruct
(
struct TestStruct
(
self = TestStruct,
a = #test,
fn printTest = (print a),
fn printAny v = (print v)
)
TestStruct = TestStruct()
ok
)
-- create an instance -- TestStruct.self()
but member “self” could be better to have protected… which we can do as:
global TestStruct
(
struct TestStruct
(
private
self = TestStruct,
public
fn CreateNew = self(),
a = #test,
fn printTest = (print a),
fn printAny v = (print v)
)
TestStruct = TestStruct()
ok
)
-- create an instance -- TestStruct.CreateNew()
as you can see you have many different ways to make a commonly used ‘library’
i usually use the method showed last.
but you should think about your tools delivery.
you have to understand that it’s not a good idea to share all your libraries with a little tool which uses just small part of all ‘library’ methods and features.
Hi denis, thanks for your super-useful examples!
They helped me a lot and really broadened my view about this stuff.
As you said, having a whole glonal library always defined in memory for a small script could be bad: I need bot structures with pure methods but some that also work on internal variables, so an instance is quite always needed.
However I see that you use a coding method quite different from what i’ve done so far (probably my approach is very rough compared to yours!), so hope you don’t mind if I dig a little more and ask you some clarification
First, you alway enclose the struct definition in a block like:
global myStruct
(
struct myStruct
(
)
)
What’s the point it? I mean, what’s the difference between this and a simple
global struct myStruct
(
)
Up to now, I’ve defined structs and then, at the end of the file, I’ve put a global
structInst = mystruct()
Is this correct? Or it’s useless to explicitly write global, since all it’s outside of a block?
And lastly… Could you explain me something more about the self and its usage? What’s the advantage to use it instead of a normal instance?
I know I’m asking a lot of stuff (and probably trivial) but I’d like to improve myself on this side and I always like to have solid theoretical foundations and not only “learn how to do things roughly and by trial and error only”
Many thanks in advance for your time!
PS how can I write code here in posts?? I only find bold,italic and quote!
- Add code here: [ code] [/code]
- Global: what DenisT does is to create a Global variable (with the same name than the struct) that holds an instance of the struct. You can code it like this too:
(
struct TestStruct
(
private
self = TestStruct,
public
fn CreateNew = self(),
a = #test,
fn printTest = (print a),
fn printAny v = (print v)
)
global TestStruct = TestStruct()
)
Or give a different name to the global instance.
Your proposed method ‘global struct myStruct (…’ would give you a syntax error.
- Usage of ‘self’: ‘self’ is an struct variable that holds the own struct definition. You make it private to be sure that no one can change it. Then you create a public method called ‘CreateNew’ to be able to generate a new instance of the struct using it (in case you need a new instance).
What’s the point in using the same name for the struct definition and for the global instance? In this case, ‘self’ holds the instance instead of the struct definition and then, you can only create instances from the global instance and not from a second one you create.
Example with DenisT’s code:
ppp = TestStruct.CreateNew() ==> you create an instance of the struct
qqq = ppp.CreateNew() ==> Error!
If you use different names as in this example:
(
struct TestStructDef
(
private
self = TestStructDef,
public
fn CreateNew = self(),
a = #test,
fn printTest = (print a),
fn printAny v = (print v)
)
global TestStruct = TestStructDef()
ok
)
You can create a new instance from a second instance:
ppp = TestStruct.CreateNew() ==> you create an instance of the struct
qqq = ppp.CreateNew() ==> you create a third instance of the struct
Few more reasons to fo it the way denisT showed:
- with global declaration outside of the brackets, when you collapse the brackets, you still know what’s inside the block
- you can have other supporting locals/stuff shared across the struct instances in the enclosed scope:
global TestStruct
(
struct helperStruct (x, y, z, w)
struct TestStruct
(
unit = helperStruct 0 0 1 0
)
TestStruct = TestStruct()
ok
)
- if you evaluate the script, the output you’ll get is the last thing returned which is ‘OK’ so the listener output is clean, too
Very, very informative stuff guys!
It was very fast and easy to change one of my library struct to this type of declaration: I’v tried, as proposed by @denisT and supported by all of you, to use the private self and everything it’s fine… and the greatest thing is that existing scripts using the struct continue to do their job.
I specifically used the last version proposed by @aaandres, thas is the closest to mine.
If I understand correctly (to recap all stuff) I have to:
- define my struct in a block between brackets (so not global)
- create a private self variable inside the struct and initialize it with the struct definition itself (using the same name)
- define a CreateNew() function that simply create an instance of self (that is the struct)
- add all my stuff to the struct definition
- After the struct definition, again inside the most external block of brackets, create the first, global instance of the struct… that will basically be the “parent” of all others (due to the fact that names are equal)
Now I could improve all my library adding some sort of conditional import at hte beginning of my scripts, instead of having a whole bunch of structs always defined in memory and maybe not used.
Thanks to all!
Just a first try…
Create a file, for example “C:\myStructsDef\Struct Case to include.ms”, with your struct definitions in a ‘case of’ shape:
(
"aaa":
(
struct aaa
(
a=1,
b=2
)
aaa()
)
"bbb":
(
struct bbb
(
st1="Hello",
st2="Goodbye"
)
bbb()
)
"ccc":
(
struct ccc
(
private
self = ccc,
public
fn CreateNew = self(),
initTime=timeStamp(),
memory=heapFree
)
ccc()
)
)
Then, in your script, if you need an instance of the ‘ccc’ struct just:
global ccc= case "ccc" of include @"C:\myStructsDef\Struct Case to include.ms"
ccc.initTime
ccc.memory
It works, but surely there are better ways. That’s for masters.
Some day I will tell about structures in MXS more because many mxs developers who work many years in development don’t really know all features and good things of using structs. But not today…
Below I show a way how I usually do it:
global MyStruct
(
struct my_struct
(
private
_def = my_struct,
_version = 0.0,
public
_name = "",
fn getDef = _def,
fn newInstance = _def(),
on create do
(
)
)
global _ms = MyStruct = my_struct()
ok
)
1 – I create a global instance of my commonly used structure
I keep the struct definition in local scope, so it’s not go in global names
2 – I end the local scope with OK to keep listener clean in case of this code execution.
Other way the whole code is going into the listener output
3 – I put the instance in a ‘simple short global’ variable for easier debugging.
I don’t care about this variable because it’s for debugging only, and I don’t use it in my scripts(tools) code
4 – I store original definition in the instance in case I want to create the struct instance with initial settings
5 – I store the definition as private because I don’t see any reason to be able to change it after creation
6 – I add _version property for future debug reasons
7 – I add _name property do be able give a unique identifier for every instance
Some rules for naming… It’s a global, so it has to have unique and not commonly used name. Also I usually add some prefix which says that is my global. The body of the global name has to tell about its area of use, for example:
unique_identifier_SplineOps
unique_identifier_UnrwapMath
unique_identifier_SkinAndCloth
unique_identifier helps me find all my stuff in the system
struct’s names inside local scope can be simple and short enough to tell the meaning
I never use include. I always try to keep all code of the tool in one file.
Where to put all tools definitions? Usually I put them in one “3rd Party Plug-Ins” paths. So they automatically execute on the system loading. Because a client has to specify this path him(her)self, he or she knows where to find all my tools later (in case of removing them for example).
A lot ofstuff here @denisT… I’ll read it a couple more times tomorrow morning!
I have to say that this quite surprises me.
Does this mean that if you should use the same struct in 2 different scripts you actually copy-and-paste it between files?
I suppose it were in order to have a complete,standalone tool in a single file… But doesn’t it force you to edit multiple files in case you change the shared struct? It seems to me harder to maintain than having an included (or fileIn) ms.
there are to ways of development
– one way is to develop a single (independent) tools for specific tasks. usually it’s a plugin or a specific tool
– the second is to develop a framework – a set of dependent tools, for example character setup, which can include: character rig tool(s), skinning tool(s), import/export, animation tool(s), etc.
in the first case you don’t need to include all you libraries. in the second the mostly all libs have to be included.
so for the first case is better to include only used methods from your lids and do it by copy and paste necessary piece of code, instead of including all libs, where only couple methods are needed and used.
for the second case you have to provide loading of all your libs, and call methods from these libs, which are already loaded in memory. in this case you don’t need to include the code in your scripts.
so the not using of include works opposite to how you think. it helps me not duplicate same code in different scripts!