• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Should More Complex Resources Require Tech Spec Doc?

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
More complex resources can be very difficult to moderate and understand. Should such resources require things like UML Diagrams or something to give visual illustration of what they do so that they can be moderated correctly?

draw.io is a website that can make such things for free. Plenty of things for Eclipse too. If you have Visio, that'll do it too.

See the following image that shows how a system works that was previously not understood.

I plan to make this standard to all of my own resources from now on myself.

147961d1440698126-system-missilerecycler-practice-visio.jpg
 
Last edited by a moderator:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
I have no idea what that diagram is about. Basically the diagram is useless without some sort of background information at least what it is meant to be showing. It looks like some sort of angle based system but that is about all one can discern from it.

How can so much be "First?". I am guessing it has some specific meaning in the context you are not mentioning?
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
How you document should be up to the author. Generally one does not need to document how something works, only what it does. If your system is particularly smart or you are trying to emphasize the quality of the solution then documenting its internal mechanics is a good idea. However if your system provides functionality then chances are people do not care how it provides the functionality, only what functionality it provides and how to access that functionality.

The diagram has too much white space on it (not very compact), the elements in it are far too large (tiny text in them) and is also at least 2-3 times too large area wise. Not everyone has UHD or high density displays so you will want to make sure if fits well within the average screen size. Consider breaking it into several smaller diagrams which you can use to illustrate along with documentation (eg like figures).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
The following is a Layer Diagram for Lists. This illustrates my earlier idea for making more organized collections that account for algorithm safety and memory safety. Still trying to figure out a diagram that shows what data plugs into what with entities ;D. I kinda do this with block diagrams : o.

I added the error message bit in a hurry, but really there is a debug layer that relies on the Error Message System. The presentation layer relies on the debug layer : ). The debug layer has all possible assertions and error messages. The presentation layer just calls the appropriate assertions for each operation.

attachment.php
 

Attachments

  • List Architecture.jpg
    List Architecture.jpg
    45.9 KB · Views: 100
Level 6
Joined
Jul 30, 2013
Messages
282
If you need an UML diagram to do something trivial, you have shitty APIs.

Also you do realise that to use some of your resources (some collections come to mind first) people basicalyl have to reverse engineer the resource. having a better api is worth 100x more than an uml diagram that nobody will find and 5% of ppl on hive can read in the non trivial case.

on the contrary what i have found most useful are some of the youtube tutorials you have posted.

also since i'm working on a map with quite a few other people with wildly varying levels of skill i can say that the best thing you can do to make your code usable is to provide a comprehensive usage sample.
"usage" should mean "you call this function with these parameters to get this effect, eg you can use these methods to tweak how the bases on the map behave and these are the effects.."
the moment you need to "implement MySpecialModule" you instantly make your resource unusable to anybody who is not a vjass expert.

a good example might be the packet library, its not exactly trivial but its pretty easy to use.
a bad example would be static stack, you need to implement half the library yourself to use it.

if i want to use a list or stack or any other language other than vjass then i dont need to implement half of it, i dont need to know any of the internals as a naive user (tho it helps sometimes for perf. if i do)
i really don't want to be mean here, since your contributions really empower many of the maps out there, including mine. but rly, there are rough corners..
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
@Waffle

I understand. Let me try to put your mind at ease : ).


The purpose of the technical documents are for power users that want to understand the code and for moderators that have a need to review the code. You will not need to read the technical documents in order to use the resource =).

For collections, I am working at revamping all of the collections so that they are easier to use. This effort includes ramping up debugging capabilities, improving code generation, and simplifying the organization of the collections. There will still be many flavors, but they will be much easier to navigate. Some of the flavors have also become invisible and are part of debug mode.

I still provide usage examples, but on top of this I also provide tutorials/labs and API listings. I also include templates.

Here is an example using List1a1a

JASS:
struct MyList extends array
    // collection fields
    public integer count

    // element fields
    public integer data

    // macro implementation
    implement List1a1a
endstruct

function Test takes nothing returns nothing
    local MyList list = MyList.create()
    local MyList node

    set node = list.push()
    set node = list.push()

    set node = list.first

    call list.pop()
endfunction


Now, sadly, these collections do kind of require that you separate your data from your behavior if you want to wrap the collections up in more complex operations while keeping your code simple : (. If only there was a way to mask module members =(. I do the below a lot. What we need is a delegate a smart delegate that will use the passed in instance, not some arbitrarily set instance, to look at another struct. What we also need is a way to hide module members, like private implement and then access the members via module.member.

JASS:
struct MyListData extends array
    // collection fields
    public integer count

    // element fields
    public integer data

    // macro implementation
    implement List1a1a
endstruct

struct MyList extends array
    public method operator count takes nothing returns integer
        return MyListData(this).count
    endmethod

    private method operator count= takes integer value returns nothing
        set MyListData(this).count = value
    endmethod

		public method operator sentinel takes nothing returns boolean
			return MyListData(this).isNull
		endmethod
		
		public method operator empty takes nothing returns boolean
			return MyListData(this).empty
		endmethod
		
		static if DEBUG_MODE and LIBRARY_ErrorMessage and LIBRARY_MemoryAnalysis then
			public method operator address takes nothing returns MemoryMonitor
				return MyListData(this).address
			endmethod
		endif
		
		public method operator collection takes nothing returns thistype
			return MyListData(this).collection
		endmethod
		
		public method operator next takes nothing returns thistype
			return MyListData(this).next
		endmethod
		
		public method operator prev takes nothing returns thistype
			return MyListData(this).prev
		endmethod
		
		public method operator first takes nothing returns thistype
			return MyListData(this).first
		endmethod
		
		public method operator last takes nothing returns thistype
			return MyListData(this).last
		endmethod
		
		public static method create takes nothing returns thistype
			local thistype this = MyListData.create()

			set count = 0

			return this
		endmethod
		
		public method destroy takes nothing returns nothing
			call MyListData(this).destroy()
		endmethod
		
		public method push takes nothing returns thistype
			set count = count + 1

			return MyListData(this).push()
		endmethod
		
		public method enqueue takes nothing returns thistype
			set count = count + 1

			return MyListData(this).enqueue()
		endmethod
		
		public method pop takes nothing returns nothing
			set count = count - 1

			call MyListData(this).pop()
		endmethod
		
		public method dequeue takes nothing returns nothing
			set count = count - 1

			call MyListData(this).dequeue()
		endmethod
		
		public method remove takes nothing returns nothing
			set count = count - 1

			call MyListData(this).remove()
		endmethod

		public method clear takes nothing returns nothing
			set count = 0

			call MyListData(this).clear()
		endmethod
endstruct

function Test takes nothing returns nothing
    local MyList list = MyList.create()
    local MyList node

    set node = list.push()
    set node = list.push()

    set node = list.first

    call list.pop()
endfunction

If we had templates, we could just do this. See how in this case we don't have to deal with the node at all? It's all hidden away. Templates would help a lot.

JASS:
struct MyStruct extends array
    integer value
endstruct

function Test takes nothing returns nothing
    local List<MyStruct> list = List<MyStruct>.create()

    call list.push(MyStruct.create())
endfunction

Inheritance, improved lists? We could inherit with that "smart" delegate I was talking about and create more complex lists if we wanted them.
 
Level 6
Joined
Jul 30, 2013
Messages
282
JASS:
    local MyList list = MyList.create()
    local MyList node

    set node = list.push()
// vs

    local List<MyStruct> list = List<MyStruct>.create()
    call list.push(MyStruct.create())

// the difference is not just the lack of generics..
// for one the actual implementation of .push takes 0 arguments,
// while the intuitive version would take the actual value to be pushed.
// same for pop, it is really confusing when you are used to pop() actually returnign a value and it doesnt.
// this leads to the added complication of having to think about should i pop/push before or
// after i get the value etc, which is not hard i admit but it adds to the cognitive burden..

// how about an api the kind of (for usage)

//! textmacro DEFINE_LIST_A1A1 takes T, STRUCTNAME
..
//! endtextmacro


// this line actually generates the entire wrapper struct that people currently have to make manually..
//! runtextmacro DEFINE_LIST_A1A1("string","JustAnotherStringList")

    local JustAnotherStringList list = JustAnotherStringList.create()
    call list.push(somestring)


// i know textmacros are kind of icky but they are there to solve a problem and i think this pretty much is the problem.
// i dont need a MySpecialNodeListOfVariantTypes most of the time, i can live with 1 type 90% of the time.
// and needing to write all that boilerplate..

// honestly i had a moment once with string stack (or was it soem queue.. cant recall..)
// i basically had my naive version done in 30 mins.
// then i realised oh-theres-a-library-for-that and thought it might be a good idea not to
// write my own buggy code when you have taken such effort to save me the bugs
// well add another hour and it was done.. (and i admit it probably save missing at least
// a few theoretical overflow bugs that i would not have bothered to fix for that use case..)
//
//.. and i learned quite a bit...
//
//
// but in order to even make it work i essentially had to reverse-engineer half your code.
// and spent more time on the lets-do-this-right-now bit than the actual productive bit.

im sorry if i hurt your feelings.. but the pain is real.. :'(
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
same for pop, it is really confusing when you are used to pop() actually returnign a value and it doesnt.

Actually, some languages have pop return something and some don't. C++, for example, returns nothing for pop.

void pop_front();

I could return something for pop, but it would have an impact on performance. Just using first does the same thing and has no performance impact.


The macro is a possibility... but I usually stay away from macros that define structs like the plague. They are less obvious than a struct with a module.



edit
From your post, I see that you definitely are not used to working with nodes instead of values. Whenever you push/pop, you are always dealing with a node. Now that I look at it, this model is actually pretty bad. It does save on some overhead as you can rely on an already existing instance.

I'll try and see if I can figure out a better model =).

I'm already seeing how we can change the API for certain cases.

Here are the cases

0a0a
0a1a
1a0a
1a1a

0t0t
0t1t
1t0t
1t1t

Making 8 cases in all.


My philosophy has been about sharing allocators between structs and lists. This isn't necessarily good. The node that gets returned as a list is meant to be used for a struct instance where you can put anything you want. This means that you can use it to allocate structs that are going to depend on the list. Now, this is actually really bad design. It should just take a value.

In cases where the collection allocator isn't present, this allows the list to use an existing allocator rather than supply its own. This means that you can typecast your struct to that collection, which is really weird and bad design.

By all accounts, the allocators should always exist!

However, if you are an optimization wtf person, then you may start mashing your allocators, sharing whatever can be shared, which is what I typically do. This means that rather than using fields, you have Object A, the owner, and Object B, the field, occupying the same space, thus in order for Object A to access Field B, you typecast Object A to Field B. It's even more than that, it's not so much a field as a struct. This makes your struct definition break up across several struct definitions and you typecast between all of them. Want a list for your struct? Make a struct definition instead of making a field and then make that struct definition use your struct's allocator to instance itself >: O. This is horrible practice and makes the code that much harder to follow, but it's really good for optimization, haha... this really should be done in an optimizer, not in the code itself. The optimizer should identify which allocators can be shared by determining if, when a struct is created, a list is always created/destroyed for it, meaning that the instances are always in sync. When this occurs, it can do the optimization for you.
 
Last edited:
Level 6
Joined
Jul 30, 2013
Messages
282
Actually, some languages have pop return something and some don't. C++, for example, returns nothing for pop.

void pop_front();
[/QUETE]

ok, fair point.

I could return something for pop, but it would have an impact on performance. Just using first does the same thing and has no performance impact.
is it really an issue?
but k lets presume that there are use cases where it is.

i'm not opposed to providing an efficcient interface. if all you need to do is do some GC then pop with no return value is fine..

but please do provide a version that does the obvious thing too.
call it like popAndReturnValue or something if you must. but please dont force me to write 3 lines of code just to add a value to a list or get the last item.

eg you can emulate a stack easily with an array and an integer.
JASS:
set sp= sp + 1
set stack[keepspace] = value
///
set value = stack[keepspace] 
set sp= sp - 1

when using your stack library essentially you need to do the exact same sub-atomic steps.
you need to allocate a new slot in the stack via .push() and then set the value..
and you need to get the value and deallocate the stack slot explicitly too.

you have no wins over just writing a plain array when it comes to usability. you still need to write out every single sub-step in the exact same order. oh but you also need to make a new struct for 0 wins.

The macro is a possibility... but I usually stay away from macros that define structs like the plague. They are less obvious than a struct with a module.

A macro either works or does not, having to write imperative code has a bunch of non obvious failure modes that people unfamiliar with the code will trip on.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
you have no wins over just writing a plain array when it comes to usability. you still need to write out every single sub-step in the exact same order. oh but you also need to make a new struct for 0 wins.

Actually, the stack is linked and not static for the module. Yours isn't linked and is static for your array. There are some benefits there.

Also, I do it like this (unless I need to set multiple values).

set stack.push().value = value

Anyways, I'll definitely see what I can do to improve the stuff. I also edited my post to talk about the weirdness of allocator presence, so be sure to give that a read.


I'm thinking of this. This would return the created node too if you want to reference it.

stack.push(node) // no allocator
stack.push(value) // allocator

stack.pop()
stack.popv() // return value

However, the value bit would require a textmacro. There is another reason I shy away from textmacros. Modules can be nested. Macros can't.

Also, this would break for any of the table implementations because the table implementations don't use macros since they have no instance limit.

JASS:
// the reason the struct definition is included is to keep certain members private
// running the macro in an existing struct would provide access to private members for the user
//! runtextmacro DEFINE_LIST_1A1A("private", "unit", "MyList")

// the following states that an allocator is not present, so an existing struct
// is used instead
//! runtextmacro DEFINE_LIST_1A0A("private", "UnitIndex" "MyList2")

//! runtextmacro DEFINE_LIST_0A1A("private", "unit" "MyList3")
//! runtextmacro DEFINE_LIST_0A0A("private", "UnitIndex" "MyList4")

function Test takes nothing returns nothing
    local MyList list
    local MyList2 list2
    local MyList3 list3
    local MyList4 list4
    local unit u = ...

    set list = MyList.create()
    call list.push(u)
    set u = list.popv()
    call list.destroy()

    set list2 = MyList2.create()
    call list2.push(UnitIndex[u])
    set u = list2.popv().unit
    call list2.destroy()

    set list3 = UnitIndex[u]
    call list3.push(u)
    set u = list3.popv(u)
    call list3.clear()

    set list4 = UnitIndex[u]
    call list4.push(UnitIndex[u])
    set u = list4.popv().unit
    call list4.clear()
endfunction

Now when we get into a table implementation, we'd need a push for each possible value. The below would likely be what it would look like.

JASS:
function Test takes nothing returns nothing
    local List1T1T list
    local List1T0T list2
    local List0T1T list3
    local List0T0T list4
    local unit u = ...

    set list = List1T1T.create()
    call list.pushUnit(u)
    set u = list.popUnit()
    call list.destroy()

    set list2 = List1T0T.create()
    call list2.push(UnitIndex[u])
    set u = list2.popv().unit
    call list2.destroy()

    set list3 = UnitIndex[u]
    call list3.pushUnit(u)
    set u = list3.popUnit(u)
    call list3.clear()

    set list4 = UnitIndex[u]
    call list4.push(UnitIndex[u])
    set u = list4.popv().unit
    call list4.clear()
endfunction

Also, there are actually 16 flavors. I forgot combinations of T and A >.<. Anything that uses A would require a module/macro and would just rely on the T allocator at a central struct. Anyways, 16 flavors * 4 collection types = 64 collections - . -.

List, CircularList, Queue, Stack

gg

edit
The more I look at this, the more it smells. I think that we should just go with AA, AT, TT, and TA. Good design is good design. The lack of allocators is REALLY bad design. When you share the same instances between several unrelated things, you end up with very deep coupling and a lot of magical code that is very hard to understand. You're not sure where anything is. Yes, there is some impact to performance and some extra code, but the result is that you get some sensible collections.

The other thing is that the era of doing modules for collections is going to end with me. I'm ending them. You shouldn't be mixing up extra logic/fields with your collections. This is just bad. It is bad. It will not be done. Macros from now on. Now, some of these modules do have macros within them from other libs, and due to the lack of nesting, I'm now going to have to inline those macros. Whatever.

This will break extending collections.... it'll break it hard -.-. Adding a count to list by wrapping it up will be next to impossible. This won't be able to be addressed until we get generics (List<Type>). Until generics, we're going to have to live with this sadly : (. This means that working on a compiler as a community project should be a priority for everyone. If anyone wants to get on board with this, please let me know.










Now, back on topic. In order to get your resource approved, if your resource merits enough complexity, I do think that it should require diagrams. The moderator should not approve it just because it works. They need to be able to review your code and design. If your resource is really nasty in complexity and has no accompanying TSD, this will make the moderator's job practically impossible.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
as was said, if your JASS(Dont forget what you are programming here, you are not programming a rocket that will send you to moon or mars) code is so complex that it needs UML diagram, you are doing it wrong and deserve not getting it approved
 
I feel like UML fits resources that have a specific pipeline (i.e. OpenGL), but even then I find them to be confusing and unnecessary (at least when I'm first exposed to the system).

Those diagrams help people understand how the system works, but a lot of end-users won't really care about that. They want to know what it does and how to use it. That can be achieved with:

(1) A proper description of the resource
(2) A good example script

Don't get me wrong, there isn't anything wrong with UML. In fact, it is a great method for outlining pipelines/MVC. But for "selling" your code, that stuff should be relegated to manuals, textbooks, etc. It is useful to have that information out there, but in order to attract people, that information should be put aside or mentioned as after thoughts. The end-user usually just wants to know the *what* and the *how*, as fast and succinct as possible.

As far as system presentation goes, I always think: "what are my typical habits when I'm trying to use someone else's code?" Too much documentation is a big drawback for lazy, inattentive farts like me. I can hardly go through the Apple docs before I hop over on stackoverflow for a basic usage sample. That being said, it isn't a bad idea to have a UML diagram, but just be sure that it serves as a complement/background to your documentation. It shouldn't replace normal documentation, and don't go out of your way to construct a diagram if your system doesn't warrant it. :)
 
Status
Not open for further replies.
Top