[SOLVED] Programming riddle : forbid the copy of a variable

Started by Monsieur OUXX, Wed 13/05/2020 12:50:30

Previous topic - Next topic

Monsieur OUXX

OK bear with me here. I'm looking to imitate what's done in C++ with ptr_unique, except in AGS. More generally I want to control every time a certain type of variable gets assigned, i.e. potentially copied.

I'll start with the "forbid copy" case and work on the other cases later. The idea is that you have a variable of some type (it doesn't matter, it can be a custom type) and the developer just cannot copy it. He/she has to work with the original instance and copying it is illegal.

As a naive solution, I could do a #define ASSIGN MyCustomFunctionThatCreatesACopyOfThePointer. Then the syntax would be clunky, but work :
Code: ags

VariableType copy = ASSIGN(originalVariable);


However what bothers me is that I could still do an "=" by mistake if I'm distracted.
Code: ags

VariableType copy = originalVariable; //I want this to be forbidden


So, the riddle :
- Can you think of any sort of mechanism (Think out of the box!) that would force a copy, or an assignment to go through an exceptional function somehow? Let me worry about the implementation. I'm looking for a clever concept to ignite it. Aren't there some obscure types in AGS that you cannot assign? Views or whatnot.
 

Crimson Wizard

#1
What kind of variable are you refering to, and why it cannot be copied?

For instance, unique_ptr in C++ denies copying of a pointer to make sure that nothing else keeps reference to that object, so that if object gets deleted it guarantees there's no links to it left (which could cause errors on use). But the object itself still can be copied.
Is your question about same situation or something different?

Monsieur OUXX

Yes, same. I want to control the number of references, in order to implement some sort of garbage collection. If the user copies the pointers around, then it breaks everything.
 

Snarky

If you're doing your own garbage collection, doesn't that mean you also have to keep track of when references are created and destroyed? And doesn't that imply that you need to manage it either through some kind of constructor/destructor or a factory/recycling plant pattern? And doesn't that give you this capability for free?

To be more explicit: the "references" could (but wouldn't have to) be instances of a managed type. You'd then be able to have multiple pointers to the same reference, but you wouldn't be able to copy the reference itself.

Monsieur OUXX

#4
Quote from: Snarky on Wed 13/05/2020 13:18:40
To be more explicit: the "references" could be instances of a managed type.

Are you saying it's not allowed to assign an instance of a managed type? If yes, then it's exactly the kind of syntactic trick I as looking for.


EDIT  I don't see how that addressed my issue :

Code: ags
managed struct Reference {
  int pointer;
};

Reference* ref1 = Factory.CreateReference(); //Side-note: What stops me from using "new" and messing up things?
Reference* ref2 = ref1 ; //I now have a copy out there in the wilderness and the garbage collector doesn't know about it.
 

Crimson Wizard

#5
Maybe I am missing something, but why do you need your own reference counting and garbage collector, and one AGS script provides is not enough?
What kind of object are we talking about here?
I just want to make sure the case does not have a trivial solution before diving into more complicated ideas.

Snarky

(Good question, CW!)

In your example, M.OUXX, the int pointer (well, all the data within the Reference instance) is the reference. That's the thing that "can't" be copied. You can have many pointers to this reference, but it won't be a copy. So when this instance is destroyed, you can decrement the reference count.

Maybe it would be clearer to not use a managed type, because it introduces confusion with the AGS pointers.

Monsieur OUXX

#7
How about this :

Code: ags

#define ArbitrarilyComplexType DynamicSprite*
#define ArbitrarilyComplexType_Index int

//Some basic array to store data externally, as AGS doesn't allow managed structs in managed structs
ArbitrarilyComplexType rawData[9999];

#define PointerType int

managed struct Pointer {
  PointerType ptrId;
  ArbitrarilyComplexType_Index someCompositeDataThatNeedsToHaveACustomDestructor;
};

Pointer* pointers[];


struct PtrFactory {
  import static PointerType Constructor(DynamicSprite* data);
  import static void Destructor(PointerType ptr);
};

PointerType PtrFactory::Constructor(DynamicSprite* data)
{
  Pointer* p = new Pointer;
  pointers[index] = p; //(not detailed : create entry in array 'pointers' and get the index)
  p.ptrId = index;

  rawData[dataIndex] = data; //(not detailed : create entry in array 'rawData' ad get the index)
  p.someCompositeDataThatNeedsToHaveACustomDestructor = dataIndex;
  
  return p.ptrId;
}

void PtrFactory::Destructor(PointerType ptr)
{
  //Destructor stuff
  ArbitrarilyComplexType objectToDestroy = rawData[pointers[ptr].someCompositeDataThatNeedsToHaveACustomDestructor];
  objectToDestroy .Delete(); //Whatever needs to be done
  pointers[ptr].ptrId = -1;
  
  pointers[ptr] = null;
}

//////////////////////////////////

managed struct Reference {
  int count;
  PointerType ptr;
};

Reference* refs[];


struct RefFactory {
  import static Reference* CreateRef(PointerType ptr);
  import static void DestroyRef(Reference*);
  import static Reference* CopyRef(Reference*);
};

Reference* RefFactory::CreateRef(PointerType ptr)
{
  // find if there's a ref contining 'ptr' in 'refs'.
  if (yes) // increase count
    refs[existingRef].count ++;
    return refs[existingRef];
  if (not) //create new ref
    refs[newRef] = new Reference;
    refs[newRef].count = 1;
    refs[newRef].ptr = ptr;
    return refs[newRef];
}

void RefFactory::DestroyRef(Reference* ref)
{
  for (int i=0; i< refs.length; i++) {
    if (refs[i] == ref) {
      if (refs[i].count > 1) {
        refs[i].count--;
      } else if (refs[i].count == 1) {
        PtrFactory.Destructor(refs[i].ptr);
        refs[i].count = 0;
        refs[i] = null;
      } else { // 0
         AbortGame("All the references to that pointer have already been destroyed. Did you do an illegal copy, you punk?");
      }
    }
  }
}

Reference* RefFactory::CopyRef(Reference* ref)
{
  for (int i=0; i< refs.length; i++) {
    if (refs[i] == ref) {
      refs[i].count++;
      return refs[i];
    }
  }
  
  AbortGame("Cannot copy illegal reference");
}

/////////////////////////////////////////////////////////

void game_start() {
    DynamicSprite* someCompositeData = DynamicSprite.Create(); // In the final product, the user would not be allowed to manipulate pointers, they'd be given the factory to create references directly.
    PointerType ptr = PtrFactory.Constructor(someCompositeData);
    Reference* someLegalRef1 = RefFactory.CreateRef(ptr);
    Reference* someLegalRef2 = RefFactory.CreateRef(ptr);
    Reference* someLegalRef3 = RefFactory.CopyRef(someLegalRef2);
    
    Reference* someIllegalRef = someLegalRef1; //Allowed syntactically, but shady
    
    RefFactory.DestroyRef(someLegalRef1);
    RefFactory.DestroyRef(someLegalRef2);
    RefFactory.DestroyRef(someIllegalRef); // Will work because this is technically someLegalRef3. This calls the Destructor of ptr, as expected.
    RefFactory.DestroyRef(someLegalRef3); // Was already destroyed just before. Causes an AbortGame;
  
}



 

Crimson Wizard

#8
Quote from: Monsieur OUXX on Wed 13/05/2020 14:32:07
managed struct Pointer {
  PointerType ptrId;
  DynamicSprite* someCompositeDataThatNeedsToHaveACustomDestructor;
};

You won't be able to do exactly this because AGS still does not allow managed pointers inside managed struct.

But still, what kind of actual object that needs custom destructor you are talking about? Can you give an actual example, allowing to understand why AGS's own garbage collection is not suitable?

Or is it that you need to do some extra custom processing when the object is destroyed?

Is the actual problem in preventing a copy, or doing custom destruction step?

Monsieur OUXX

Quote from: Crimson Wizard on Wed 13/05/2020 14:38:02
Quote from: Monsieur OUXX on Wed 13/05/2020 14:32:07
managed struct Pointer {
  DynamicSprite* someCompositeDataThatNeedsToHaveACustomDestructor;
};

You won't be able to do exactly this because AGS still does not allow managed pointers inside managed struct.
That was a shortcut. It would actually be the index of an element stored in an external array of managed structs. Therefore, an int.

Quote from: Crimson Wizard on Wed 13/05/2020 14:38:02
But still, what kind of actual object that needs custom destructor you are talking about? Can you give an actual example, allowing to understand why AGS's own garbage collection is not suitable?
How about this : An array of arrays of custom structs, each struct containing an array of arrays of int. All of that mess being represented only as ints (that int being the index in the actual array of structs, or array of ints, or array of arrays of ints) since, as you pointed out, AGS won't let you do any of that using pointers.
In other words : I want arbitrarily complex data structures, and I want automated garbage collection for them.
 

Crimson Wizard

#10
Quote from: Monsieur OUXX on Wed 13/05/2020 14:49:33
How about this : An array of arrays of custom structs, each struct containing an array of arrays of int. All of that mess being represented only as ints (that int being the index in the actual array of structs, or array of ints, or array of arrays of ints) since, as you pointed out, AGS won't let you do any of that using pointers.
In other words : I want arbitrarily complex data structures, and I want automated garbage collection for them.

Oh I see. Well, I'd say that real automatic disposal of such structs will be impossible given that AGS does not support destructors. So you will have to call a function to destroy such collection or parts of it.

Frankly, at this point I would decide to stop bothering inventing complicated stuff and just write a narrow specialized type meant to be used with dedicated functions, in C style. Sure it won't be C++ let alone C# level of safety or genericness, but at least it will be clear.
This is my personal advice, but you may ignore it of course.

Monsieur OUXX

#11
Quote from: Crimson Wizard on Wed 13/05/2020 15:02:13
I would just write a narrow specialized type meant to be used with dedicated functions
That's the complicated stuff. Hundreds and hundreds of copy-and-paste of the same array allocation and deallocation, over the last 15 years. The thing is that people who come from the C++ and C world are so used to it that they don't see it anymore. They accept that 90% of any code is "allocate array, expand array, insert item into array, remove item from array, shrink array". I haven't given up yet.

Anyways I got my answer. The idea of making the reference a managed type clears 90% of the question. Thanks, guys.
 

Crimson Wizard

#12
Quote from: Monsieur OUXX on Wed 13/05/2020 15:08:25
Quote from: Crimson Wizard on Wed 13/05/2020 15:02:13
I would just write a narrow specialized type meant to be used with dedicated functions
That's the complicated stuff. Hundreds and hundreds of copy-and-paste of the same array allocation and deallocation, over the last 15 years. The thing is that people who come from the C++ and C world are so used to it that they don't see it anymore. They accept that 90% of any code is "allocate array, expand array, insert item into array, remove item from array, shrink array". I haven't given up yet.

That's like not giving up trying to break a hole in a wall with your head, instead of working on a real solution.
The two potential solutions, really:
1) improve AGS scripting language
2) integrate better scripting language into AGS.

Another option is plugins, where everything is done in real language, and only interface is in script.
Everything else is diverted by inability to store pointers in pointers, and other restrictions of the language.

Crimson Wizard

Oh, and
Quote from: Monsieur OUXX on Wed 13/05/2020 15:08:25The thing is that people who come from the C++ and C world are so used to it that they don't see it anymore. They accept that 90% of any code is "allocate array, expand array, insert item into array, remove item from array, shrink array".

this is a fucking bullshit. We are not "used to it" and DO "see it", because in both C and C++ is perfectly possible to achieve generic storage and algorithms, and in case of C++ automatic garbage collection (less auto in C).
So it has nothing to do with the language someone came from. It's acceptance of AGS limitations as a real fact, and that you can spend your time more efficiently than inventing more hacks for it.

Monsieur OUXX

#14
Well you do have the C++ experience and the understanding of the AGS virtual instructions that I'm lacking to implement an actual Array object, or to allow managed structs inside managed structs. But you said that an actual object for dynamic arrays was not really needed. Verbatim. You gave me that standard C++ talk of "there are so many ways of implementing arrays of arrays if you're smart".
I tried fixing AGS it in C++ several times, but never got to the point where I understood its virtual memory space well enough. Then my last resort is to address my needs in native AGS.
 

Crimson Wizard

#15
First of all, I am sorry for getting so agitated over this.


Quote from: Monsieur OUXX on Wed 13/05/2020 15:38:01But you said that an actual object for dynamic arrays was not really needed. Verbatim. You gave me that standard C++ talk of "there are so many ways of implementing arrays of arrays if you're smart".

Not precisely, iirc I said that
1. It's impossible to make generic class in AGS script, and this is why I was reluctant to make one specifically for ints. I was and still am in doubts whether this is the best way to proceed.
2. Furthermore, having a managed object which encapsulates array won't solve the problem with complex structs, because you still cannot store pointers in managed structs. So max you will be able to do is to have an array of array objects, or regular struct with array object inside.

But it's true, I claimed that it's possible to do multidimensional array of ints stored in a dynamic array of ints, and I still believe it is.

The important point is, though, it won't have automatic reference counting of internal objects, or automatic disposal of these. Users will have to discipline themselves to use specialized functions with such array and not go inside "by hand".
Latter is what I meant when saying "narrow specialized type meant to be used with dedicated functions, in C style". I apologize for not being clear enough. By "C style" I mean - no automation - you still have to use functions to instigate any operation with an object. And yes, it can break if used incorrectly.



In relation to this thread's topic, apparently I misunderstood your intention about what kind of copy prevention is suitable for you. Of course, if you don't give user an actual object but some reference, then occasional copying of object is not going to happen.
Looking at your code prototype above, since the general plan is for users to not use pointers directly, but references, and use special functions to create and copy the references, then it also may be that the Reference managed struct may be replaced with a simple "int", called "handle". This handle would serve as an ID of some object inside your system.

E.g. in COM and similar libraries there functions like AddRef and Release that accept handles and change reference counts, which may also be stored internally.

But this, of course, is not automatic and may break if user forgets to call CopyReference/AddRef and DestroyRef/Release.
Getting object's interal data is still a problem. It may be solved with Get/Set functions/attributes, or returning a physical copy of object (unreferenced).

Monsieur OUXX

In case I didn't make it clear, I'm trying to address my needs with my own hacky solutions precisely because I know you're already giving 200% to AGS and it would not be very sport to whine to you about what I consider missing features. Or at least more than once a year ;) the flow of releases is already outstanding for an open-source project of this size.

Leave this be for now.
 

Crimson Wizard

#17
Quote from: Monsieur OUXX on Wed 13/05/2020 16:12:18I know you're already giving 200% to AGS

Not anymore for a long time, for various reasons, and one of them is that I question the past direction of development. Too much effort and time was given to hacks and patches, where bigger breaking change ought to be (and topics like this only prove it).

SMF spam blocked by CleanTalk