Skip to content

Commit e4450bc

Browse files
user1ajkhoury
user1
authored andcommitted
I added some changes to make the generated classes compile properly without having to be manually reordered.
Summary: * When the 'generate' button is clicked, each class is added to a Class Dependency Graph, and each class that class references is added as a depenedency. * There are two dependency types - POINTER and INSTANCE. * POINTER references can be solved with a forward declaration. However, a simple reordering can solve that dependency and limit the forward declaration spam at the top of the generated file. * INSTANCE references can only be solved with reordering. If class B contains an instance of class A, A must come before B in the generated file, no way around it. * The graph is then recursively traversed - starting at nodes with no dependencies - with classes being added to the 'ordered' class collection. * Circular dependencies (A points to B points to C points to A) are solved with forward declarations (C comes before B comes before A, with A being forward declared) * The result is the output has fewer forward declarations and classes don't need to be manually reordered. In my DayZ Reclass file (which inspired this work, because it takes me a lot of time manually reordering every time I generate the output) there are only 5 forward declarations with 356 classes. Details: The graph class itself is pretty simple. It maintains a mapping of CNodeClass pointers to DependencyNodes. Each node knows what class it represents, and has two vectors of edges - incoming edges (another class depends on this one) and outgoing edges (this class depends on another). Each edge knows what node it points to, as well as whether the dependency is of POINTER or INSTANCE type. Edges are generated by walking each node in a class and adding a dependency edge for each pointer, instance, instance array, and pointer array. Parallel edges (duplicates) are not added, nor are simple recursive edges (a class has a pointer to an instance of that same class). Other types (function ptrs? Can we prototype those in ReClass?) may be necessary here, but these were all I could figure out without asking someone. ClassDependencyGraph depGraph; // Add each class as a node to the graph before adding dependency edges for (auto cNode : this->m_Classes) { depGraph.AddNode(cNode); } for (auto cNode : this->m_Classes) { for (size_t n = 0; n < cNode->NodeCount(); n++) { CNodeBase* pNode = (CNodeBase*)cNode->GetNode(n); NodeType Type = pNode->GetType(); switch (Type) { case(nt_pointer): { CNodePtr* pPointer = (CNodePtr*)pNode; CNodeClass* pointerClass = pPointer->GetClass(); depGraph.AddEdge(cNode, pointerClass, DependencyType::POINTER); break; } case(nt_instance): { CNodeClassInstance* pClassInstance = (CNodeClassInstance*)pNode; CNodeClass* instanceClass = pClassInstance->GetClass(); depGraph.AddEdge(cNode, instanceClass, DependencyType::INSTANCE); break; } case(nt_array): { CNodeArray* pArray = (CNodeArray*)pNode; CNodeClass* instanceClass = pArray->GetClass(); depGraph.AddEdge(cNode, instanceClass, DependencyType::INSTANCE); break; } case(nt_ptrarray): { CNodePtrArray* pArray = (CNodePtrArray*)pNode; CNodeClass* pointerClass = pArray->GetClass(); depGraph.AddEdge(cNode, pointerClass, DependencyType::POINTER); break; } } } } Ordering dependencies in the graph is simple in naive cases. Just start at nodes that have no dependencies (leaf nodes) and recursively visit dependencies, adding them to an ordered class vector as you go. This works great until you hit circular dependencies - you'd recurse until death. That's where forward declarations are needed. If you hit a node that is already being processed, you know you've thrown that ass in a circle. So that's when you throw your hands up and forward declare - but you can only do that for a POINTER dependency, not an INSTANCE one. You can see in this graph that DayZPlayer instances Entity which points to physicsObject which points to DayZPlayer. And if that's the order you hit the nodes in, you're fine - just forward declare DayZPlayer. However, there exists a path from gpWorld -> World -> N0000120E -> Entity -> PhysicsObject -> DayZPlayer -> Entity. In that case, when you realize you're in a cycle you're at an INSTANCE dependency and you can't solve it with a forward declaration. I wasn't sure what the correct way to handle this was, so I kind of improvised a solution. At the beginning of graph solving, all nodes which have incoming INSTANCE type dependencies are marked. Then, when they're visited during recursion, if they're being visited across a POINTER dependency edge, they're forward declared and not processed further. So when you hit gpWorld -> World -> N0000120E -> Entity, it knows Entity has incoming INSTANCE dependencies, so instead of recursing and ending up in DayZPlayer, it adds a forward declaration for Entity and bails out. Then, later (and along a different path that doesn't go through Entity), DayZPlayer is visited normally and it recurses into Entity, and all is well. With forward declarations generated and the classes in dependency order, output goes on as normal, except instead of iterating of m_Classes, it iterates over the set of forward declarations and the in-order vector of classes instead. To generate the visualized graph, I put a ToDot method in the DependencyGraph, which generates a GraphViz Dot representation of the graph. I left it in as it may end up being useful in troubleshooting in the future.
1 parent 57190ae commit e4450bc

6 files changed

+316
-4
lines changed

ReClass/ClassDependencyGraph.cpp

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#include "stdafx.h"
2+
#include "ClassDependencyGraph.h"
3+
4+
5+
bool DependencyNode::AddInEdge(const DependencyNode* parent, const DependencyType edgeType) {
6+
DependencyEdge newEdge(parent, edgeType);
7+
//
8+
// We do not allow parallel edges, so we check to make sure this edge doesn't already exist.
9+
// We COULD have used a map for the dependencies container. But, most classes probably won't
10+
// have a ton of dependencies, so a vector makes sense for our more average case.
11+
//
12+
for (auto edge : this->inEdges) {
13+
if (edge == newEdge) {
14+
return false;
15+
}
16+
}
17+
this->inEdges.push_back(newEdge);
18+
return true;
19+
}
20+
21+
const std::vector<DependencyEdge>* DependencyNode::GetDependencies() const {
22+
return &this->dependencies;
23+
}
24+
25+
bool DependencyNode::AddDependency(const DependencyNode* dep, const DependencyType edgeType) {
26+
DependencyEdge newEdge(dep, edgeType);
27+
//
28+
// We do not allow parallel edges, so we check to make sure this edge doesn't already exist.
29+
// We COULD have used a map for the dependencies container. But, most classes probably won't
30+
// have a ton of dependencies, so a vector makes sense for our more average case.
31+
//
32+
for (auto edge : this->dependencies) {
33+
if (edge == newEdge) {
34+
return false;
35+
}
36+
}
37+
this->dependencies.push_back(newEdge);
38+
return true;
39+
}
40+
41+
42+
std::vector<DependencyNode*> ClassDependencyGraph::GetLeafNodes() {
43+
std::vector<DependencyNode*> result;
44+
for (auto it = nodes.begin(); it != nodes.end(); it++) {
45+
if (it->second.GetInEdges()->size() == 0) {
46+
result.push_back(&it->second);
47+
}
48+
}
49+
return result;
50+
}
51+
52+
std::vector<DependencyNode*> ClassDependencyGraph::GetInstanceNodes() {
53+
std::vector<DependencyNode*> result;
54+
for (auto it = nodes.begin(); it != nodes.end(); it++) {
55+
bool hasOutInstanceEdge = false;
56+
57+
for (auto inEdge : *it->second.GetInEdges()) {
58+
if (inEdge.edgeType == DependencyType::INSTANCE) {
59+
hasOutInstanceEdge = true;
60+
break;
61+
}
62+
}
63+
if (hasOutInstanceEdge) {
64+
result.push_back(&it->second);
65+
}
66+
}
67+
return result;
68+
}
69+
70+
int ClassDependencyGraph::ProcessDependencies(DependencyEdge* edge, std::map<const DependencyNode*, NodeGenerationStatus>& nodeStatus, std::set<const CNodeClass*>& forwardDeclarations, std::vector<const CNodeClass*> &classDefinitions) {
71+
int classesAdded = 0;
72+
NodeGenerationStatus nodeGenStatus;
73+
const DependencyNode* node = edge->dependency;
74+
if (nodeStatus.find(node) == nodeStatus.end()) {
75+
nodeStatus[node] = NodeGenerationStatus::UNPROCESSED;
76+
}
77+
nodeGenStatus = nodeStatus[node];
78+
// We have to treat instanced nodes differently, since they have 'hard' dependencies
79+
// on their parent classes. If we come across an instanced node through a pointer,
80+
// we will forward declare it because if we try to process it like normal we may end
81+
// up with a back edge to its parent that will break everything.
82+
//
83+
// If we come upon an instanced node througn an instance, we process it like normal.
84+
if (nodeGenStatus == NodeGenerationStatus::INSTANCED) {
85+
if (edge->edgeType == DependencyType::POINTER) {
86+
forwardDeclarations.insert(node->GetCClass());
87+
return 0;
88+
}
89+
else if (edge->edgeType == DependencyType::INSTANCE) {
90+
// Change nodeGenStatys to trigger the actual processing of the node
91+
nodeGenStatus = NodeGenerationStatus::UNPROCESSED;
92+
}
93+
}
94+
if (nodeGenStatus == NodeGenerationStatus::PROCESSED) {
95+
// The node is already written to the output, no further processing needed
96+
return classesAdded;
97+
}
98+
else if (nodeGenStatus == NodeGenerationStatus::PROCESSING) {
99+
// We've hit a back edge. Since we start from leaf nodes, this dependency cannot
100+
// possibly be an instance dependency, it must be a pointer dependency. Thus, a
101+
// forward declaration is enough to handle this case.
102+
ASSERT(edge->edgeType == DependencyType::POINTER);
103+
forwardDeclarations.insert(node->GetCClass());
104+
// While it isn't actually finished processing, if we leave it as PROCESSING we'll
105+
// get a new forward declaration every time we hit this node. If it's currently
106+
// processing, somewhere up the call stack is the actual processing of this node
107+
// so on return it'll get finished up.
108+
nodeStatus[node] = NodeGenerationStatus::PROCESSED;
109+
}
110+
else {
111+
// Case where processing has yet to begin. We need to recursively process all dependencies,
112+
// then add this class to the ordered list of definitions.
113+
nodeStatus[node] = NodeGenerationStatus::PROCESSING;
114+
for (auto dep : *node->GetDependencies()) {
115+
if (dep.edgeType == DependencyType::INSTANCE)
116+
classesAdded += ProcessDependencies(&dep, nodeStatus, forwardDeclarations, classDefinitions);
117+
}
118+
for (auto dep : *node->GetDependencies()) {
119+
if (dep.edgeType == DependencyType::POINTER)
120+
classesAdded += ProcessDependencies(&dep, nodeStatus, forwardDeclarations, classDefinitions);
121+
}
122+
classDefinitions.push_back(node->GetCClass());
123+
nodeStatus[node] = NodeGenerationStatus::PROCESSED;
124+
classesAdded += 1;
125+
}
126+
return classesAdded;
127+
}
128+
129+
std::string ClassDependencyGraph::ToDot(std::string graphLabel) {
130+
std::stringstream stream;
131+
stream << "digraph class_dependency {";
132+
for (auto node : this->nodes) {
133+
for (auto edge : *node.second.GetDependencies()) {
134+
stream << "\"";
135+
stream << CT2CA(node.first->GetName());
136+
stream << "\"";
137+
stream << " -> ";
138+
stream << "\"";
139+
stream << CT2CA(edge.dependency->GetCClass()->GetName());
140+
stream << "\"";
141+
if (edge.edgeType == DependencyType::POINTER) {
142+
stream << " [style=dotted]";
143+
}
144+
stream << ";";
145+
stream << "\n";
146+
}
147+
}
148+
stream << "}";
149+
return stream.str();
150+
}
151+
152+
int ClassDependencyGraph::OrderClassesForGeneration(std::set<const CNodeClass*>& forwardDeclarations, std::vector<const CNodeClass*>& classDefinitions) {
153+
int classesAdded = 0;
154+
std::map<const DependencyNode*, NodeGenerationStatus> nodeStatus;
155+
std::vector<DependencyNode*> leafNodes = this->GetLeafNodes();
156+
std::vector<DependencyNode*> instancedNodes = this->GetInstanceNodes();
157+
158+
for (auto node : instancedNodes) {
159+
nodeStatus[node] = NodeGenerationStatus::INSTANCED;
160+
}
161+
162+
for (auto leaf : leafNodes) {
163+
for (auto dep : *leaf->GetDependencies()) {
164+
classesAdded += ProcessDependencies(&dep, nodeStatus, forwardDeclarations, classDefinitions);
165+
}
166+
classDefinitions.push_back(leaf->GetCClass());
167+
classesAdded += 1;
168+
}
169+
170+
return classesAdded;
171+
}
172+

ReClass/ClassDependencyGraph.h

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#pragma once
2+
#include "CNodeClass.h"
3+
4+
#include <assert.h>
5+
#include <atlbase.h>
6+
#include <fstream>
7+
#include <iostream>
8+
#include <map>
9+
#include <set>
10+
#include <string>
11+
#include <sstream>
12+
#include <vector>
13+
14+
class DependencyNode;
15+
16+
enum class DependencyType {
17+
POINTER,
18+
INSTANCE
19+
};
20+
21+
struct DependencyEdge {
22+
const DependencyNode* dependency;
23+
const DependencyType edgeType;
24+
25+
DependencyEdge(const DependencyNode* dependency, const DependencyType edgeType) : dependency(dependency), edgeType(edgeType) {};
26+
bool operator==(const DependencyEdge& other) const {
27+
return this->dependency == other.dependency && this->edgeType == other.edgeType;
28+
}
29+
};
30+
31+
class DependencyNode {
32+
const CNodeClass* cClass = nullptr;
33+
std::vector<DependencyEdge> dependencies;
34+
std::vector<DependencyEdge> inEdges;
35+
public:
36+
DependencyNode() {};
37+
DependencyNode(const CNodeClass* cClass) : cClass(cClass) {};
38+
const std::vector<DependencyEdge>* GetDependencies() const;
39+
const std::vector<DependencyEdge>* GetInEdges() const { return &this->inEdges; }
40+
const CNodeClass* GetCClass() const { return this->cClass; }
41+
bool AddDependency(const DependencyNode* dep, const DependencyType edgeType);
42+
bool AddInEdge(const DependencyNode* parent, const DependencyType edgeType);
43+
};
44+
45+
class ClassDependencyGraph {
46+
std::map<const CNodeClass*, DependencyNode> nodes;
47+
48+
std::vector<DependencyNode*> GetLeafNodes();
49+
std::vector<DependencyNode*> GetInstanceNodes();
50+
enum class NodeGenerationStatus {
51+
UNPROCESSED,
52+
PROCESSING,
53+
PROCESSED,
54+
INSTANCED
55+
};
56+
int ProcessDependencies(DependencyEdge *node, std::map<const DependencyNode*, NodeGenerationStatus> &nodeStatus, std::set<const CNodeClass*>& forwardDeclarations, std::vector<const CNodeClass*> &classDefinitions);
57+
58+
59+
public:
60+
bool AddNode(const CNodeClass* node) {
61+
if (this->nodes.find(node) == this->nodes.end()) {
62+
this->nodes[node] = DependencyNode(node);
63+
return true;
64+
}
65+
return false;
66+
}
67+
68+
bool AddEdge(const CNodeClass *dependingClass, const CNodeClass* dependency, DependencyType depType) {
69+
// Ignore simple recursion in the dependency graph. It won't help in our analysis and will just muddy
70+
// up the graph.
71+
if (dependingClass == dependency) {
72+
return false;
73+
}
74+
DependencyNode* dependingNode = &this->nodes[dependingClass];
75+
DependencyNode* dependencyNode = &this->nodes[dependency];
76+
dependencyNode->AddInEdge(dependingNode, depType);
77+
return dependingNode->AddDependency(dependencyNode, depType);
78+
}
79+
std::string ToDot(std::string graphLabel = "");
80+
int OrderClassesForGeneration(std::set<const CNodeClass*>& forwardDeclarations, std::vector<const CNodeClass*> &classDefinitions);
81+
};

ReClass/ReClassEx.cpp

+53-4
Original file line numberDiff line numberDiff line change
@@ -1284,10 +1284,59 @@ void CReClassExApp::OnButtonGenerate( )
12841284

12851285
if (!m_strHeader.IsEmpty( ))
12861286
strGeneratedText += m_strHeader + _T( "\r\n\r\n" );
1287+
std::set<const CNodeClass*> forwardDeclarations;
1288+
std::vector<const CNodeClass*> orderedClassDefinitions;
1289+
ClassDependencyGraph depGraph;
1290+
// Add each class as a node to the graph before adding dependency edges
1291+
for (auto cNode : this->m_Classes) {
1292+
depGraph.AddNode(cNode);
1293+
}
1294+
for (auto cNode : this->m_Classes) {
1295+
for (size_t n = 0; n < cNode->NodeCount(); n++)
1296+
{
1297+
CNodeBase* pNode = (CNodeBase*)cNode->GetNode(n);
1298+
NodeType Type = pNode->GetType();
1299+
switch (Type) {
1300+
case(nt_pointer):
1301+
{
1302+
CNodePtr* pPointer = (CNodePtr*)pNode;
1303+
CNodeClass* pointerClass = pPointer->GetClass();
1304+
depGraph.AddEdge(cNode, pointerClass, DependencyType::POINTER);
1305+
break;
1306+
}
1307+
case(nt_instance):
1308+
{
1309+
CNodeClassInstance* pClassInstance = (CNodeClassInstance*)pNode;
1310+
CNodeClass* instanceClass = pClassInstance->GetClass();
1311+
depGraph.AddEdge(cNode, instanceClass, DependencyType::INSTANCE);
1312+
break;
1313+
}
12871314

1288-
for (size_t i = 0; i < m_Classes.size( ); i++)
1315+
case(nt_array):
1316+
{
1317+
CNodeArray* pArray = (CNodeArray*)pNode;
1318+
CNodeClass* instanceClass = pArray->GetClass();
1319+
depGraph.AddEdge(cNode, instanceClass, DependencyType::INSTANCE);
1320+
break;
1321+
}
1322+
1323+
case(nt_ptrarray):
1324+
{
1325+
CNodePtrArray* pArray = (CNodePtrArray*)pNode;
1326+
CNodeClass* pointerClass = pArray->GetClass();
1327+
depGraph.AddEdge(cNode, pointerClass, DependencyType::POINTER);
1328+
break;
1329+
}
1330+
}
1331+
}
1332+
}
1333+
1334+
depGraph.OrderClassesForGeneration(forwardDeclarations, orderedClassDefinitions);
1335+
ASSERT(orderedClassDefinitions.size() == m_Classes.size());
1336+
for (auto forwardDeclared : forwardDeclarations)
12891337
{
1290-
t.Format( _T( "class %s;\r\n" ), m_Classes[i]->GetName( ) );
1338+
CNodeClass* pClass = (CNodeClass*)forwardDeclared;
1339+
t.Format( _T( "class %s;\r\n" ), pClass->GetName( ) );
12911340
strGeneratedText += t;
12921341
}
12931342

@@ -1298,9 +1347,9 @@ void CReClassExApp::OnButtonGenerate( )
12981347

12991348
CString ClassName;
13001349

1301-
for (UINT c = 0; c < m_Classes.size( ); c++)
1350+
for (auto constantClass : orderedClassDefinitions)
13021351
{
1303-
CNodeClass* pClass = m_Classes[c];
1352+
CNodeClass* pClass = (CNodeClass*)constantClass;
13041353

13051354
CalcOffsets( pClass );
13061355

ReClass/ReClassEx.h

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include "DialogConsole.h"
1111
// Symbols
1212
#include "Symbols.h"
13+
// Class dependency graph
14+
#include "ClassDependencyGraph.h"
1315

1416
class CReClassExApp : public CWinAppEx {
1517
public:

ReClass/ReClassEx.vcxproj

+2
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@
332332
<ItemGroup>
333333
<ClInclude Include="CCustomEdit.h" />
334334
<ClInclude Include="CCustomToolTip.h" />
335+
<ClInclude Include="ClassDependencyGraph.h" />
335336
<ClInclude Include="CNodeArray.h" />
336337
<ClInclude Include="CNodeBase.h" />
337338
<ClInclude Include="CNodeBits.h" />
@@ -413,6 +414,7 @@
413414
<ClCompile Include="CCustomToolTip.cpp" />
414415
<ClCompile Include="CClassFrame.cpp" />
415416
<ClCompile Include="CClassView.cpp" />
417+
<ClCompile Include="ClassDependencyGraph.cpp" />
416418
<ClCompile Include="CNodeArray.cpp" />
417419
<ClCompile Include="CNodeBase.cpp" />
418420
<ClCompile Include="CNodeBits.cpp" />

ReClass/ReClassEx.vcxproj.filters

+6
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@
392392
<ClInclude Include="CNodePtrArray.h">
393393
<Filter>Header Files\Nodes</Filter>
394394
</ClInclude>
395+
<ClInclude Include="ClassDependencyGraph.h">
396+
<Filter>Header Files</Filter>
397+
</ClInclude>
395398
</ItemGroup>
396399
<ItemGroup>
397400
<ClCompile Include="stdafx.cpp">
@@ -568,6 +571,9 @@
568571
<ClCompile Include="CNodePtrArray.cpp">
569572
<Filter>Source Files\Nodes</Filter>
570573
</ClCompile>
574+
<ClCompile Include="ClassDependencyGraph.cpp">
575+
<Filter>Source Files</Filter>
576+
</ClCompile>
571577
</ItemGroup>
572578
<ItemGroup>
573579
<Image Include="res\menu_modify.bmp">

0 commit comments

Comments
 (0)