Skip to content

Conversation

@martinweismann
Copy link
Member

@martinweismann martinweismann commented Aug 24, 2022

Original author @anpilog in #169.

This PR introduces RTTI for ACT components and adds a docker setup to the project.
It is based on #141 and #142.

RTTI

In a few words the PR addresses missed part of ACT - original type of an object that has been passed from library to application side.

Let's look on an example.
AnimalIterator class has a method GetNextAnimal() method that returns instance of Animal.
image
But in fact, actual object could be any of derived animal classes.
So the signature of the method is defined. And this is what ACT generates today:
image
It creates shared pointer to an instance of Animal class. Just like defined in IDL file.
However, this is not what actually happens under the hood. Animal objects on library side belong to other classes.
This information just gets lost through ACT because it passes objects as a typeless handle.

IMHO, ideally, class type should be just a part of Handle type. Instead of void * it could be a struct with two fields: void* and a classId.
But in this PR for a simplicity of reviewing the idea I leave handle as void * and just add class type id.

Each class has unique Class Type Id in a form of 64 bits unsigned integer. It's generated as 64 most significant bits of SHA1 for a string NameSpace::ClassName, where NameSpace is a namespace defined in IDL file and ClassName is an actual name of a given class.

Base class has a virtual method uint64 ClassIdType(). In C++ implementation it belongs to interfaces layer and thus completely hidden from developers.
Actual name is configured in IDL by global setting classtypeidmethod. It's autogenerated by ACT and returns class type id.

On a bindings side (application) ACT implements hidden from developer PolymorphicFactory() function that is used to instantiate objects from handle. Instead of std::make_shared on C++, for example.
It serves only one goal - instantiate correct class on an application side that matches class of a handle on a library side.
C++ example:

template <class T>
std::shared_ptr<T> CWrapper::polymorphicFactory(RTTIHandle pHandle)
{
	RTTI_uint64 resultClassTypeId = 0;
	CheckError(nullptr, m_WrapperTable.m_Base_ClassTypeId(pHandle, &resultClassTypeId));
	switch(resultClassTypeId) {
		case 0x1549AD28813DAE05UL: return std::dynamic_pointer_cast<T>(std::make_shared<CBase>(this, pHandle)); break; // First 64 bits of SHA1 of a string: "RTTI::Base"
		case 0x8B40467DA6D327AFUL: return std::dynamic_pointer_cast<T>(std::make_shared<CAnimal>(this, pHandle)); break; // First 64 bits of SHA1 of a string: "RTTI::Animal"
		....
	}
	return std::make_shared<T>(this, pHandle);
}

and then used instead of std::make_shared in class method implementation for class parameters passed as out or return.

/**
* CAnimalIterator::GetNextAnimal - Return next animal
* @return 
*/
PAnimal CAnimalIterator::GetNextAnimal()
{
    RTTIHandle hAnimal = nullptr;
    CheckError(m_pWrapper->m_WrapperTable.m_AnimalIterator_GetNextAnimal(m_pHandle, &hAnimal));
    
    if (hAnimal) {
        return m_pWrapper->polymorphicFactory<CAnimal>(hAnimal);
    } else {
        return nullptr;
    }
}

I already mentioned above that direct call to ClassTypeId() method to retrieve object class is not optimal and could be optimized.
It's better passing class type with a handle. Either as part of Handle type or by extra parameter of a C function call.

This PR includes implementation for C++, Python and Pascal bindings and C++ with Pascal Stubs.

On an application side code relies on a language native capabilities:
C++

while (auto animal = iter->GetNextAnimal()) {
	std::cout << "Animal name: " << animal->Name() << std::endl;
	if (auto tiger = std::dynamic_pointer_cast<CTiger>(animal)) {
		std::cout << "  ^ is a real tiger!!!" << std::endl;
		tiger->Roar();
	}
}

Python

while animal := iter.GetNextAnimal():
    if isinstance(animal, RTTI.Tiger):
        print("  ^ is a real tiger!!!")
        animal.Roar()
    print("Animal name: " + animal.Name())

Pascal:

Animal := Iterator.GetNextAnimal();
while Animal <> nil do
begin
    writeln('Animal name: ', Animal.Name());
    if Animal is TRTTITiger then 
    begin
        writeln('  ^ is a real tiger!!!');
        Tiger := Animal as TRTTITiger;
        Tiger.Roar();
    end;
    Animal := Iterator.GetNextAnimal();
end;

NOTES and TODO:

  • Check functionality in case of injected components - done at RTTI: typed object handle anpilog/AutomaticComponentToolkit#1
  • String that is used for calculating SHA1 hash should include whole hierarchy of parent classes. Theoretically, library can change data model without breaking ABI. And in this case it's safer to ignore RTTI and instantiate explicitly defined class in IDL. Looks like it's not necessary as objects are always resolved in corresponded PolymorphicFactory.

Docker setup

As developer, you can now run

./Build/docker.sh build    # to build docker image
./Build/docker.sh act      # to build ACT binaries 
./Build/docker.sh examples # to build and run projects in Examples folder
./Build/docker.sh all      # to build ACT binaries and then build and run projects in Examples folder
./Build/docker.sh cli      # to open bash session inside Docker with source code mapped to '/data' directory

The build/Dockerfile contains all layers required to build ACT and to build and run the Examples RTTI and Injection, i.e. ./Build/docker.sh builds the Cpp and Pascal implementations of these libraries, builds the example code in the binding languages C, Cpp, CppDynamic, CSharp, Go, Java, Pascal and Python and runs them.

Every push and PR now executes ./Build/docker.sh all.

alexanderoster and others added 12 commits August 24, 2022 11:28
* Make C# object with native handle disposable

The wrapper user can decide to release the native handle immediately to avoid potential resource race issues.

* Implement dispose pattern following the Microsoft documentation

https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose

* Refine the change according to pull request comments

Co-authored-by: Kevin Zhang <Ping.Zhang@autodesk.com>
Co-authored-by: Martin Weismann <30837766+martinweismann@users.noreply.github.com>
* First try for Sphinx documentation

* Add more documentation

* Prepare for more documentation-generation

* Fixup build
@martinweismann martinweismann changed the title Rtti native 3 Native Run-Time Type Information support and automatic builds of examples. Aug 24, 2022
@martinweismann martinweismann changed the title Native Run-Time Type Information support and automatic builds of examples. Native Run-Time Type Information support and automatic builds of examples Aug 24, 2022
@martinweismann martinweismann marked this pull request as ready for review August 24, 2022 11:57
@martinweismann martinweismann changed the title Native Run-Time Type Information support and automatic builds of examples Native Run-Time Type Information support Aug 25, 2022
@martinweismann martinweismann merged commit 70934cc into Autodesk:develop Aug 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants