From 24ed563e9e55683ea09a18d1374e267267e82151 Mon Sep 17 00:00:00 2001 From: uck Date: Tue, 22 Apr 2025 18:18:46 +0200 Subject: [PATCH] Add C++ binding thread safety options --- Source/automaticcomponenttoolkit.go | 2 +- Source/buildbindingccpp.go | 92 ++++++++++++++++++++++++++--- Source/componentdefinition.go | 43 ++++++++++++++ 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/Source/automaticcomponenttoolkit.go b/Source/automaticcomponenttoolkit.go index 2cea8aa2..71b36c83 100644 --- a/Source/automaticcomponenttoolkit.go +++ b/Source/automaticcomponenttoolkit.go @@ -584,7 +584,7 @@ func printUsageInfo() { } func main() { - ACTVersion := "1.8.0-alpha" + ACTVersion := "1.8.1-alpha" fmt.Fprintln(os.Stdout, "Automatic Component Toolkit v"+ACTVersion) if len(os.Args) < 2 { printUsageInfo() diff --git a/Source/buildbindingccpp.go b/Source/buildbindingccpp.go index c54855f8..c43b7791 100644 --- a/Source/buildbindingccpp.go +++ b/Source/buildbindingccpp.go @@ -591,7 +591,7 @@ func writeDynamicCPPMethodDeclaration(method ComponentDefinitionMethod, w Langua } func writeDynamicCPPMethod(method ComponentDefinitionMethod, w LanguageWriter, NameSpace string, ClassIdentifier string, ClassName string, - implementationLines []string, isGlobal bool, includeComments bool, doNotThrow bool, useCPPTypes bool, ExplicitLinking bool, forWASM bool) error { + implementationLines []string, isGlobal bool, includeComments bool, doNotThrow bool, useCPPTypes bool, ExplicitLinking bool, forWASM bool, classThreadSafetyOption ThreadSafetyOption) error { WASMPrefix := "" WASMCast := "" @@ -875,6 +875,12 @@ func writeDynamicCPPMethod(method ComponentDefinitionMethod, w LanguageWriter, N } w.Writeln(" {") + + addThreadSafeCalls := (classThreadSafetyOption == eThreadSafetySoft && requiresInitCall) || classThreadSafetyOption == eThreadSafetyStrict + if addThreadSafeCalls { + w.Writeln(" m_pWrapper->%s(this);", getLockHandleMethodName()) + } + w.Writelns(" ", definitionCodeLines) if requiresInitCall { w.Writeln(" %s%s(%s)%s;", checkErrorCodeBegin, CMethodName, initCallParameters, checkErrorCodeEnd) @@ -888,6 +894,10 @@ func writeDynamicCPPMethod(method ComponentDefinitionMethod, w LanguageWriter, N w.Writelns(" ", implementationLines) } + if addThreadSafeCalls { + w.Writeln(" m_pWrapper->%s(this);", getUnlockHandleMethodName()) + } + if len(returnCodeLines) > 0 { w.Writeln(" ") w.Writelns(" ", returnCodeLines) @@ -1450,13 +1460,14 @@ func writePolymorphicFactoryImplementation(w LanguageWriter, component Component w.Writeln("}") } -func writeCheckErrorImplementation(w LanguageWriter, ErrorMethodName string, ClassIdentifier string, cppBaseClassName string, NameSpace string) { +func writeCheckErrorImplementation(w LanguageWriter, ErrorMethodName string, ClassIdentifier string, cppBaseClassName string, NameSpace string, MultiThreadedEnv bool) { w.Writeln(" inline void C%sWrapper::CheckError(%s * pBaseClass, %sResult nResult)", ClassIdentifier, cppBaseClassName, NameSpace) w.Writeln(" {") w.Writeln(" if (nResult != 0) {") w.Writeln(" std::string sErrorMessage;") w.Writeln(" if (pBaseClass != nullptr) {") w.Writeln(" %s(pBaseClass, sErrorMessage);", ErrorMethodName) + w.Writeln(" %s(pBaseClass);", getUnlockHandleMethodName()) w.Writeln(" }") w.Writeln(" throw E%sException(nResult, sErrorMessage);", NameSpace) w.Writeln(" }") @@ -1464,6 +1475,55 @@ func writeCheckErrorImplementation(w LanguageWriter, ErrorMethodName string, Cla w.Writeln(" ") } +func writeLockHandleImplementation(w LanguageWriter, ClassIdentifier string, cppBaseClassName string) { + w.Writeln(" inline void C%sWrapper::%s(%s * pBaseClass)", ClassIdentifier, getLockHandleMethodName(), cppBaseClassName) + w.Writeln(" {") + w.Writeln(" %s.lock();", getLockMapMutexName()) + w.Writeln(" auto it = %s.find(pBaseClass->handle());", getLockMapName()) + w.Writeln(" if (it != %s.end()) {", getLockMapName()) + w.Writeln(" it->second.first += 1;") + w.Writeln(" }") + w.Writeln(" else {") + w.Writeln(" it = %s.emplace(pBaseClass->handle(), std::make_pair(1, std::make_shared())).first;", getLockMapName()) + w.Writeln(" }") + w.Writeln(" %s.unlock();", getLockMapMutexName()) + w.Writeln(" it->second.second->lock();") + w.Writeln(" }") + w.Writeln(" ") +} + +func writeUnlockHandleImplementation(w LanguageWriter, ClassIdentifier string, cppBaseClassName string) { + w.Writeln(" inline void C%sWrapper::%s(%s * pBaseClass)", ClassIdentifier, getUnlockHandleMethodName(), cppBaseClassName) + w.Writeln(" {") + w.Writeln(" %s.lock();", getLockMapMutexName()) + w.Writeln(" auto it = %s.find(pBaseClass->handle());", getLockMapName()) + w.Writeln(" if (it != %s.end()) {", getLockMapName()) + w.Writeln(" it->second.second->unlock();") + w.Writeln(" if (--it->second.first == 0) {") + w.Writeln(" %s.erase(pBaseClass->handle());", getLockMapName()) + w.Writeln(" }") + w.Writeln(" }") + w.Writeln(" %s.unlock();", getLockMapMutexName()) + w.Writeln(" }") + w.Writeln(" ") +} + +func getLockMapMutexName() (string) { + return "m_lockMapMutex" +} + +func getLockMapName() (string) { + return "m_lockMap" +} + +func getLockHandleMethodName() (string) { + return "LockHandle" +} + +func getUnlockHandleMethodName() (string) { + return "UnlockHandle" +} + func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace string, BaseName string, ClassIdentifier string, ExplicitLinking bool) error { useCPPTypes := true @@ -1510,6 +1570,10 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s w.Writeln("#include ") w.Writeln("#include ") w.Writeln("#include ") + if component.isMultiThreadedEnv() { + w.Writeln("#include ") + w.Writeln("#include ") + } w.Writeln("#include ") w.Writeln("#include ") w.Writeln("") @@ -1540,7 +1604,8 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s w.Writeln(" ") w.Writeln(" inline void CheckError(%s * pBaseClass, %sResult nResult);", cppBaseClassName, NameSpace) - w.Writeln("") + w.Writeln(" inline void %s(%s * pBaseClass);", getLockHandleMethodName(), cppBaseClassName) + w.Writeln(" inline void %s(%s * pBaseClass);", getUnlockHandleMethodName(), cppBaseClassName) for j := 0; j < len(global.Methods); j++ { method := global.Methods[j] @@ -1559,6 +1624,11 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s w.Writeln(" s%sDynamicWrapperTable m_WrapperTable;", NameSpace) } + if component.isMultiThreadedEnv() { + w.Writeln(" std::mutex %s;", getLockMapMutexName()) + w.Writeln(" std::unordered_map<%sHandle, std::pair>> %s;", NameSpace, getLockMapName()) + } + if len(component.ImportedComponentDefinitions) > 0 { w.Writeln(" // Injected Components") for _, subComponent := range component.ImportedComponentDefinitions { @@ -1622,7 +1692,7 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s implementationLines = append(implementationLines, fmt.Sprintf(" throw E%sException(%s_ERROR_COULDNOTLOADLIBRARY, \"Unknown namespace \" + %s);", NameSpace, strings.ToUpper(NameSpace), sParamName)) } - err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, false) + err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, false, eThreadSafetyNone) if err != nil { return err } @@ -1630,7 +1700,11 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s } w.Writeln("") - writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace) + writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace, component.isMultiThreadedEnv()) + w.Writeln("") + writeLockHandleImplementation(w, ClassIdentifier, cppBaseClassName) + w.Writeln("") + writeUnlockHandleImplementation(w, ClassIdentifier, cppBaseClassName) w.Writeln("") if ExplicitLinking { @@ -1648,7 +1722,7 @@ func buildCppHeader(component ComponentDefinition, w LanguageWriter, NameSpace s w.Writeln(" */") for j := 0; j < len(class.Methods); j++ { method := class.Methods[j] - err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, false) + err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, false, class.eThreadSafetyOption()) if err != nil { return err } @@ -2217,14 +2291,14 @@ func buildCppwasmGuestHeader(component ComponentDefinition, w LanguageWriter, Na implementationLines = append(implementationLines, fmt.Sprintf(" throw E%sException(%s_ERROR_COULDNOTLOADLIBRARY, \"Unknown namespace \" + %s);", NameSpace, strings.ToUpper(NameSpace), sParamName)) } - err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, true) + err = writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, "Wrapper", implementationLines, true, true, false, useCPPTypes, ExplicitLinking, true, eThreadSafetyNone) if err != nil { return err } } w.Writeln("") - writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace) + writeCheckErrorImplementation(w, component.Global.ErrorMethod, ClassIdentifier, cppBaseClassName, NameSpace, false) w.Writeln("") for i := 0; i < len(component.Classes); i++ { @@ -2236,7 +2310,7 @@ func buildCppwasmGuestHeader(component ComponentDefinition, w LanguageWriter, Na w.Writeln(" */") for j := 0; j < len(class.Methods); j++ { method := class.Methods[j] - err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, true) + err := writeDynamicCPPMethod(method, w, NameSpace, ClassIdentifier, class.ClassName, make([]string, 0), false, true, false, useCPPTypes, ExplicitLinking, true, eThreadSafetyNone) if err != nil { return err } diff --git a/Source/componentdefinition.go b/Source/componentdefinition.go index 24be9903..743ecd8b 100644 --- a/Source/componentdefinition.go +++ b/Source/componentdefinition.go @@ -62,6 +62,13 @@ const ( eSpecialMethodBuildinfo = 9 ) +type ThreadSafetyOption int +const ( + eThreadSafetyNone = 0 + eThreadSafetySoft = 1 + eThreadSafetyStrict = 2 +) + // ComponentDefinitionParam definition of a method parameter used in the component's API type ComponentDefinitionParam struct { ComponentDiffableElement @@ -90,6 +97,7 @@ type ComponentDefinitionClass struct { ClassName string `xml:"name,attr"` ClassDescription string `xml:"description,attr"` ParentClass string `xml:"parent,attr"` + ThreadSafetyOption string `xml:"threadsafetyoption,attr"` Methods []ComponentDefinitionMethod `xml:"method"` } @@ -796,6 +804,14 @@ func descriptionIsValid(description string) bool { return false; } +func threadSafetyOptionIsValid(threadSafetyOption string) bool { + switch threadSafetyOption { + case "none", "strict", "soft": + return true + } + return false +} + func isScalarType(typeStr string) bool { switch (typeStr) { case "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "bool", "single", "double", "pointer": @@ -1405,6 +1421,33 @@ func (component *ComponentDefinition) countMaxOutParameters() (uint32) { return maxOutParameters; } +func (class * ComponentDefinitionClass ) eThreadSafetyOption() (ThreadSafetyOption) { + switch class.ThreadSafetyOption { + case "strict": + return eThreadSafetyStrict + case "soft": + return eThreadSafetySoft + case "none": + return eThreadSafetyNone + } + return eThreadSafetyNone +} + +func (class *ComponentDefinitionClass) isThreadSafe() (bool) { + return class.eThreadSafetyOption() != eThreadSafetyNone +} + +func (component *ComponentDefinition) isMultiThreadedEnv() (bool) { + classes := component.Classes + for i := 0; i < len(classes); i++ { + class := classes[i] + if class.isThreadSafe() { + return true + } + } + return false; +} + func (component *ComponentDefinition) classTypeIdMethod() (ComponentDefinitionMethod) { var method ComponentDefinitionMethod baseClass := component.baseClass()