000

Index Labels

Allocation Adventures 1: The DataComponent

.

When sketching out a new low level system I always start with the data layout, because that's the most important part. And I do that with two main goals:

  1. Make sure that memory is laid out and accessed linearly.
  2. Minimize the number of individual memory allocations.

The importance of the first goal should be obvious to anybody who has been following the data-oriented design movement. Modern CPUs are often memory bound. Writing cache friendly code is paramount. Etc.

The advantages of the second goal are less obvious.

Too some extent, it goes hand-in-hand with the first. For the cache to work efficiently you need to avoid pointer chasing, which naturally leads to fewer allocations. But there are other advantages as well.

Data with fewer individual allocations puts less pressure on the memory system. It gives you less fragmentation, more control over memory usage, allocation patterns that are easier to profile and optimize, etc.

It also makes the data easier to move and copy, since it requires less pointer patching, which let's you do lots of cool tricks with it. In general, it's just neater.

For static data, such as resources loaded from disk, both these goals can be easily achieved, by using the blob method.

For dynamic data, things get trickier. If you just use STL as you are told to, you end up with scary stuff like std::map< std::string, std::vector<std::string> > where the cache and memory usage is all over the place.

So let's try and see how we can escape that monster's lair by looking at a component and improving it step-by-step until we get to something nicer.

Introducing the DataComponent

The DataComponent is a part of our entity system that is used to store small amounts of arbitrary dynamic data for an entity. For example, it can be used to store a character sheet:

name = "The One"
stats = {
health = 100
mana = 200
}
status_effects = {
drunk = true
delirious = true
}

As data format we use a restricted form of JSON where the only allowed values are:

  • booleans
  • floats
  • strings
  • objects
  • arrays of numbers

Note that arbitrary arrays are not allowed. Arrays can only be lists of numbers, i.e. [0 0.5 0.5 0.7]. If you want to have sets of other items, you need to use an object with unique values (possibly GUIDs) as the keys.

The reason for these restrictions is that we want to make sure that all operations on the data merge in simple and well-defined ways. This makes it easier to use the data in collaborative workflows.

Strings and float arrays are regarded as monolithic with respect to merging. This means that all data operations can be reduced to setting object keys, which makes them easy to reason about.

We assume that the amount of data stored for each entity will be small, but that lots of entities will have data and that it is important to keep memory use low and performance high.

The naive approach

Representing a tree structure that can include both strings and arrays is pretty complicated and if we just start to sketch the structure using our regular STL tools it quickly becomes pretty messy:

enum class DataType {BOOL, FLOAT, STRING, OBJECT, ARRAY};
struct DataValue
{
DataType type;
union {
bool b;
float f;
std::string *s;
std::vector<float> *array;
std::map<std::string, DataValue> *object;
};
};

That doesn't look very fun at all. There are tons of allocations everywhere, both explicit (std::string *) and implicit (inside string, vector, and map). So let's start working.

1: Hash the keys

If we consider how we will use this data structure, we will always be setting and getting the values corresponding to certain keys. We don't have a pressing need to extract the key strings themselves. This means we can use hashed strings instead of the strings themselves for lookup.

Since we assume that the number of keys will be small, it is probably enough to use an unsigned rather than an uint64_t for the hash value:

std::map<unsigned, DataValue> *object;

Great start. That's a lot of allocations already gone and as a bonus we also got rid of a lot of string comparisons.

2: Flatten the structure

If we also sacrifice the ability of being able to enumerate all the (hashed) keys in a particular object we can go even further and flatten the entire structure.

That is, instead of representing the data as:

name = "The One"
stats = {
health = 100
mana = 200
}
status_effects = {
drunk = true
delirious = true
}

We will use:

name = "The One"
stats.health = 100
stats.mana = 200
status_effects.drunk = true
status_effects.delirious = true

Just as before we represent these keys as hashed values, so "stats.health" is represented by an unsigned containing the hashed value of "stats.health".

(Note: we won't actually hash the string "stats.health" directly, because that could lead to problems if some of our keys contained a "." character. Instead we hash each sub key separately and then hash them all together.)

With this approach, the user will still be able to look up a particular piece of data, such as stats.health, but she won't be able to enumerate all the keys and values under stats, since the hierarchy information is lost. But that's ok.

Getting rid of the tree structure allows us to store our data in a flat array:

enum class DataType {BOOL, FLOAT, STRING, OBJECT, ARRAY};
struct Entry
{
unsigned key;
DataType type;
union {
bool b;
float f;
std::string *s;
std::vector<float> *array;
};
};
std::vector<Entry> data;

Note that we no longer have a lookup hierarchy for the entries. To find a particular key we have to linearly search std::vector<Entry> for a match. Luckily, linear search is the best thing caches know and for reasonable sizes, the search will run faster than a std::map lookup.

We could sort the entries and do a binary search, but since the number of entries will be small, it is probably not even worth bothering.

3: Rewrite using a structure-of-arrays approach

One thing that does slow the search for a particular key down is all the additional data we need to load into the cache to perform the search. As we scan the vector for a particular key, the cache will also be filled by the type and value data.

We can solve this with the standard techinque of rewriting our "array of structures" as a "structure of arrays". I.e., we break out each field of the structure into its own array:

enum class DataType {BOOL, FLOAT, STRING, OBJECT, ARRAY};
struct Value
{
union {
bool b;
float f;
std::string *s;
std::vector<float> *array;
}
};
std::vector<unsigned> keys;
std::vector<DataType> types;
std::vector<Value> values;

Now when searching for a particular key, we only need to load the keys table which means the cache only contains useful data.

4: Co-allocate the arrays

Not too shabby, but we still have three separate allocations for the three vectors. To get rid of that we have to stop using these fancy std::vectors and go back to the basics, and in terms of basics, a std::vector<unsigned> is really just:

struct {
int capacity;
int size;
unsigned *data;
};

So we can represent our three vectors as:

int capacity;
int size;
unsigned *keys;
DataType *types;
Value *values;

Note that as a bonus, we can now share the capacity and the size fields between the vectors, since they all have the same values and change together.

The arrays are also all reallocated at the same time. We can make use of this and instead of doing three separate allocations, we just allocate one big buffer and lay them out sequentially in that buffer:

       -----------------------------------
buffer | keys | types | values |
-----------------------------------

Which in code would be something like:

char *buffer = allocate(capacity * (sizeof(unsigned) + sizeof(DataType) + sizeof(Value));
keys = (unsigned *)buffer;
types = (DataType *)(keys + capacity);
values = (Value *)(types + capacity);

Presto, now we have all the header data stored in a single buffer.

5: Get rid of STL

We still have these pointers to deal with in the values union:

std::string *s;
std::vector<float> *array;

Let's start by getting rid of those STL types. At this point they are no longer helping. STL is based around individual allocations which we don't want. We also have two levels of indirection there. First, the pointer to the std::vector, and then the pointer to the actual data inside the std::vector type.

So, we just replace them with their non-STL equivalents:

struct {
char *data;
} s;
struct {
int capacity;
int size;
float *data;
} array;

This actually bloats the size of the Value union from 64 to 128 bits, which is less than stellar, but have faith, we are going in the right direction.

6: Put the value data in a buffer

We can now repeat the same trick that we did with the header data and put all the strings and float arrays in a big single buffer:

-------------------------------------------------------------
| "hello" | [0 1 3] | "mana" | [0 2] | ... unused space ... |
-------------------------------------------------------------

To allocate some data we just keep track of this buffer and how much of it is currently in use:

struct value_buffer
{
char *p;
unsigned capacity;
unsigned size;
};

void *allocate_memory(value_buffer &vb, unsigned size)
{
if (vb.size + size > vb.capacity)
return nullptr;
auto res = vb.p + vb.size;
vb.size += size;
return res;
}

There are two things we need to take care of here. First, we may run out of space in this buffer as we add more data. Second, the individual values we store may change size and no longer fit in their designated place.

The first issue is no big problem. If we run out of space, we can just allocate a bigger buffer as std::vector does.

The second issue is no biggie either. If an item needs to grow, we just allocate a new bigger slot for it from the buffer. This will leave a hole where the item was originally located:

-------------------------------------------------------------
| ....... | [0 1 3] | "mana" | [0 2] | "hello more" | ..... |
-------------------------------------------------------------

These "holes" will cause fragmentation of the buffer and eventually make it run out of space. If this was a general purpose memory allocator that would be a serious issue that we would have to be seriously worried about.

But in our case it doesn't really matter. The number of items is small and we control the pointers to them, so we can easily move them around and defragment the buffer when necessary. But in fact, we don't even have to do that. If we get fragmentation we will eventually run out of space. This will reallocate the buffer which will also defragment it. This is enough for our purposes.

7: Slim down the Value struct

Since all our value data are now allocated in a single buffer we can use the buffer offset rather than the pointer to refer to the data. This saves us memory (from 64 bits to 32 bits) and as an added bonus, it also allows us to relocate the buffer without having to do pointer patching.

For these small snippets of data 64 K is plenty, which means we can fit both the offset and the size in 32 bits:

struct Data {
uint16_t offset;
uint16_t size;
};

(If you need more memory, you could use a full uint32_t for the offset and store the size in the buffer, together with the data.)

So know we have:

struct Value
{
union {
bool b;
float f;
Data data;
}
};

where the data field is used for both strings and arrays. This means our Value structure is now just 32 bits, half the size of the STL version.

8: Merge the final two allocations

Now we are down to just two buffers: one for the header (keys and types), and one for the values (strings and arrays). Wouldn't it be great if we could merge them to use just a single allocation?

Let's try our old trick and putting them together in the same buffer:

--------------------------------------------------------
| Header | Values | ....... free space ...... |
--------------------------------------------------------

This doesn't really work, because now the Header part can't grow unless we move the values out of the way.

We could give them each a little bit of free space:

----------------------------------------------------------
| Header | ... free ... | Values | ... free ... |
----------------------------------------------------------

Now they both have some room to grow, but we are not utilizing the free space to its maximum potential. The Values section may run out of free space before the Header, forcing us to reallocate the buffer even though we actually have some free space left in it. That's a bummer.

But what if we do this:

----------------------------------------------------------
| Header | ........ free space ........ | Values |
----------------------------------------------------------

Instead of allocating the values bottom up, we allocate them top down in the buffer. Now the headers and the values can share the same free space and we don't have to reallocate the buffer until they meet in the middle. Nice!

Benefits of single buffer structures

Stripping everything down to a single buffer gives us many of the same benefits that the blob approach gives us for static data.

Since the data is stored in a single buffer and doesn't contain any pointers it is fully relocatable. We can move it around in memory as we please and make copies of it with a simple memcpy().

If we want to save the data to disk, as a static resource or as part of a saved game, we don't need any special serialization code. We can just write and read the data directly.

The pointer arithmetic that is needed to manipulate data in this way may seem complex at first, if you are not used to think about pointer operations on raw memory. But I would argue that once you get used to that, this kind of representation is in many ways simpler than the multi-level std::vector, std::map based approach that we started with. And in that simplicity lies a lot of power.

But wait, I have been cheating!

Yes, to be honest I have been cheating all this time.

I have ignored the old adage of data-oriented programming:

Where there is one, there are many.

All this time I have been talking about how one DataComponent can store its data in a single buffer. But in our real system we will have many DataComponents. We might have 1000 entities, each with a DataComponent that stores a single health float. Even if each component uses a contiguous buffer, that's still a lot of individual memory allocations.

Wouldn't it be nice if we could somehow bundle them together too?

But that represents a trickier challenge. We need to find a way to allocate a lot of individual objects in a single buffer in such a way that each individual object can grow and shrink independently.

And that will be the topic for another post.

Blog Archive

Labels

.NET Programming 2D Drafting 3D 3D Animation 3D Art 3D Artist 3D CAD 3D Character 3D design 3D design tutorial 3D Drafting 3D effects 3D Engineering 3D Lighting 3D Materials 3D Modeling 3D models 3D Navigation 3D presentation 3D Printing 3D rendering 3D scanning 3D scene 3D simulation 3D Sketch Inventor 3D Texturing 3D visualization 3D Web App 3ds Max 4D Simulation ACC Adaptive Clearing adaptive components Add-in Development Additive Layers Additive Manufacturing Advanced CAD features Advanced Modeling advanced plot styles Advanced Sketch AEC Technology AEC Tools AEC Workflow affordable Autodesk tools AI AI animation AI Assistance AI collaboration AI Design AI Design Tools AI Experts AI for Revit AI Guide AI in 3D AI in Architecture AI in CAD AI in CNC AI in design AI in engineering AI in Manufacturing AI in Revit AI insights AI lighting AI rigging AI Strategies AI Tips AI Tools AI Tricks AI troubleshooting AI workflow AI-assisted AI-assisted rendering AI-Assisted Workflow AI-enhanced AI-powered templates Animation Animation Curves Animation Layers animation pipeline animation tips Animation Tutorial Animation workflow annotation Annotation Scaling annotation standards Annotations AR Architectural AI Architectural CAD architectural design Architectural Drawing architectural drawings architectural modeling architectural preservation Architectural Productivity architectural visualization Architecture architecture CAD architecture design Architecture Engineering Architecture Firm Architecture Productivity architecture projects architecture software architecture technology architecture tools Architecture Visualization Architecture Workflow Arnold Renderer Arnold Shader Artificial Intelligence As-Built Model assembly techniques Asset Management augmented reality Auto Rig Maya AutoCAD AutoCAD advice AutoCAD AI tools AutoCAD API AutoCAD automation AutoCAD Basics AutoCAD Beginner AutoCAD Beginners AutoCAD Blocks AutoCAD Civil 3D AutoCAD Civil3D AutoCAD commands AutoCAD efficiency AutoCAD Expert Advice AutoCAD features AutoCAD File Management AutoCAD Guide AutoCAD Hub AutoCAD Layer AutoCAD Layers AutoCAD learning AutoCAD print settings AutoCAD productivity AutoCAD scripting AutoCAD Scripts AutoCAD Sheet Set tips AutoCAD Teaching AutoCAD Techniques AutoCAD Templates AutoCAD tips AutoCAD tools AutoCAD training. AutoCAD tricks AutoCAD Tutorial AutoCAD workflow AutoCAD Xref Autodesk Autodesk 2025 Autodesk 2026 Autodesk 3ds Max Autodesk AI Autodesk AI Tools Autodesk Alias Autodesk AutoCAD Autodesk BIM Autodesk BIM 360 Autodesk Certification Autodesk Civil 3D Autodesk Cloud Autodesk community forums Autodesk Construction Cloud Autodesk Docs Autodesk Dynamo Autodesk features Autodesk for Education Autodesk Forge Autodesk FormIt Autodesk Fusion Autodesk Fusion 360 Autodesk help Autodesk InfraWorks Autodesk Inventor Autodesk Inventor Frame Generator Autodesk Inventor iLogic Autodesk Knowledge Network Autodesk License Autodesk Maya Autodesk mistakes Autodesk Navisworks Autodesk news Autodesk plugins Autodesk productivity Autodesk Recap Autodesk resources Autodesk Revit Autodesk Software Autodesk support ecosystem Autodesk Takeoff Autodesk Tips Autodesk training Autodesk tutorials Autodesk update Autodesk Upgrade Autodesk Vault Autodesk Video Autodesk Viewer Automate automate drawing updates Automate Printing automate publishing automate repetitive tasks Automated Design automated publishing Automated Sheets Automation Automation in AutoCAD Automation Tools Automation Tutorial automotive design automotive visualization Backup Basic Commands Basics batch drawing validation Batch Plot Batch Plotting Beginner beginner CAM Beginner Tips beginner tutorial beginners guide Bend Tools Best Practices Big Data BIM BIM 360 BIM Challenges BIM collaboration BIM Compliance BIM Coordination BIM Data BIM Design BIM Efficiency BIM for Infrastructure BIM Implementation BIM Library BIM Management BIM modeling BIM software BIM Standards BIM technology BIM Tips BIM tools BIM Trends BIM workflow Block Editor Block Management Block Organization Boolean Operations Building design Building Design Software Building Efficiency Building Maintenance building modeling Building Systems Building Technology business tools ByLayer CAD CAD API CAD assembly CAD Automation CAD best practices CAD Blocks CAD CAM CAD collaboration CAD commands CAD comparison CAD consistency CAD Customization CAD Data Management CAD Design CAD drawing checks CAD efficiency CAD errors CAD Evolution CAD file management CAD File Size Reduction CAD Integration CAD Learning CAD libraries CAD line thickness CAD management CAD Migration CAD mistakes CAD modeling CAD Optimization CAD organization CAD Oversight CAD plugins CAD Productivity CAD project management CAD Projects CAD Rendering CAD Scripting CAD Security CAD Sheet Management CAD sheet sets CAD Shortcuts CAD Skills CAD software CAD software 2026 CAD software training CAD standardization CAD standards CAD Tables CAD team CAD teams CAD technology CAD templates CAD Tips CAD Tools CAD Tracking CAD tricks CAD Tutorial CAD version control CAD workflow CAD workflow optimization CAD workflows CAM CAM Best Practices CAM for beginners CAM Optimization CAM simulation CAM strategies CAM Tips CAM tutorial CAM Workflow car design software Case Study central hub Central Hub Solutions centralized commands centralized documentation centralized management Centralized Sheet Set centralizing CAD CEO Guide CG Workflow CGI CGI design Character Animation Character Rig Character Rigging cinematic lighting Civil 3D Civil 3D hidden gems Civil 3D productivity Civil 3D tips civil design software civil engineering Civil engineering software Clash Detection Class-A surfacing clean CAD file cleaning command client engagement Cloth Simulation Cloud CAD cloud CAD storage Cloud Collaboration Cloud design platform Cloud Engineering Cloud Management Cloud Storage Cloud-Based CAD Cloud-First CNC CNC machining collaboration collaboration in CAD Collaboration Tools Collaborative CAD collaborative design Collaborative Drafting color management command abbreviations Complex Projects Complex Renovation concept car conceptual workflow Connected Design construction Construction Analytics Construction Automation Construction BIM Construction Cloud construction documentation construction drawings construction management Construction Phases Construction Planning Construction Project Construction Projects Construction Scheduling Construction Technology construction tools construction tracking Contractor contractor tools Contractor Workflow Contraints corridor design Cost Effective Design cost estimation Create resizable blocks Creative Teams creative tools CTB CTB STB Custom Hatch custom scripts custom tool palettes Custom visual styles Cutting Parameters Cybersecurity Data Backup Data Extraction data management Data Protection Data Reference Data Security Data Shortcut deadline tracking Demolition Design Design Automation Design Career Design Collaboration Design Comparison Design consistency Design Coordination Design Documentation design efficiency Design Engineering design errors Design Hacks Design Innovation design management design optimization Design Options Design Oversight design productivity design review Design Reviews design revisions Design Rules design software design software tips design standardization design standards Design Teams Design Technology design templates design tips Design Tools design tracking Design Workflow design-to-construction Designer designer hacks Designer Tools Designer Workflow Digital Art Digital Assets Digital Construction Digital Construction Technology Digital Content Digital Design Digital Drafting digital drawing Digital engineering digital fabrication Digital Library Digital Manufacturing digital marketing digital takeoff Digital Thread Digital Tools Digital Transformation Digital Twin Digital Twins digital workflow dimension dimension styles dimensioning Disaster Recovery document management Document Organization Documentation drafting drafting automation Drafting Efficiency Drafting productivity Drafting Shortcuts Drafting Standards Drafting Tips drafting tools Drafting Workflow Drawing Drawing Accuracy Drawing Automation drawing consistency drawing management Drawing Organization drawing revisions Drawing standards drawing templates drawing tips Dref DWG files DXF Export Dynamic Block Dynamic Block AutoCAD Dynamic Blocks dynamic data management Dynamic doors Dynamic windows Dynamics Dynamics Simulation Dynamo Dynamo automation early stage design eco design editing commands Efficiency efficient CAD efficient project management Electrical Systems Emerging Features Energy Analysis energy efficiency Energy Simulation Engineering Engineering Automation engineering CAD engineering data Engineering Design Engineering Documentation Engineering Drawing engineering drawings engineering efficiency Engineering Innovation Engineering Productivity engineering projects Engineering Skills engineering software Engineering Technology engineering tips engineering tools Engineering Tools 2025 Engineering Workflow Error Reduction Excel Export Workflow Express Tools External Reference Fabric Simulation facial animation Facial Rigging Facility Management Families Fast Structural Design faster delivery Field Documentation file auditing File Management file naming File Optimization File Recovery Fire Flame flange tips flat pattern Fluid Effects Fluid Simulation Forge Development Forge Viewer FreeCAD Fusion 360 Fusion 360 API Fusion 360 guide Fusion 360 Tips Fusion 360 tutorial Future of Design Future Skills Game Design Game Development Game Effects Gamification Generative Design Geospatial Data GIS Global design teams global illumination GPU Acceleration grading optimization Graph Editor Green Architecture green building Green Technology Grips Handoff Hatch Patterns HDRI health check Healthcare Facilities heavy CAD file Heavy CAD Files heritage building conservation hidden commands Hospital Design Hub Workflows HVAC HVAC Design Tools HVAC Engineering HVAC Optimization Hydraulic Modeling IK/FK iLogic Import Workflow Industrial Design Industry 4.0 Infrastructure infrastructure design Infrastructure Monitoring Infrastructure Planning Infrastructure Technology InfraWorks innovation Insight Intelligent AutoCAD Hub Intelligent automation Intelligent Design intelligent modeling Intelligent Repetition Control Intelligent Sheet Management Intelligent Sheet Sets intelligent tools Intelligent Workflow Interactive Design interactive presentation Interior Design Inventor Inventor API Inventor Drawing Template Inventor Frame Generator Inventor Graphics Issues Inventor IDW Inventor Tips Inventor Tutorial IoT ISO 19650 joints Keyboard Shortcuts keyframe animation Keyframe generation Landscape Design Large Projects Laser Scan layer conventions Layer Management Layer Organization layer standards layouts Learn AutoCAD Legacy CAD Library components Licensing light techniques Lighting Lighting and shading Lighting Techniques lineweight Linked Models Liquid Machine Learning Machine Learning in CAD Machine Optimization Machining Efficiency machining productivity Macros maintenance command Manage multiple projects from a single hub with a centralized project management system that improves collaboration Management manual plotting manufacturing Manufacturing Innovation Manufacturing Technology Mapping Technology marketing visuals master sheet index Material Creation Material Libraries Maya Maya Animation Maya character animation Maya lighting Maya Python Maya Rigging Maya Shader Maya Tips Maya tutorial Maya Workflow measurement Mechanical Design Mechanical Engineering Media & Entertainment MEP MEP Modeling Mesh-to-BIM Metal Fabrication Metal Structure milestone tracking modal analysis Model Clarity Model Management Model Optimization model space Modeling Secrets Modular Housing Monitoring Progress Motion capture Motion Design motion graphics motion simulation MotionBuilder Multi Office Workflow multi-axis machining Multi-Body Modeling Multi-Project Multi-Project Management Multi-User Environment multileader multiple sheet sets naming convention Navisworks Navisworks Best Practices nCloth Net Zero Design New Construction ObjectARX .NET API Open Source CAD Optimization Organization OVERKILL OVERKILL AutoCAD Override Layers Page Setup Palette paper space parametric assembly Parametric Components Parametric Constraints parametric design parametric family Parametric Modeling particle effects particle systems PDF PDF Export PDM system Personal Brand Phase Filters Phasing photorealism Photorealistic photorealistic render PlanGrid plot automation Plot Settings Plot Style Plot Style AutoCAD plot styles Plotting Plotting automation Plugin Tutorial Plumbing Design PM Tools point cloud Portfolio Post Construction Post-Processing Practice Drawing precision machining preconstruction workflow predictive analysis predictive animation Predictive Maintenance Predictive rigging Prefabrication Preloaded families Presentation-ready visuals Printing Printing Quality Problem Solving Procedural animation procedural motion Procedural Rig Procedural Textures Product Design Product Development product lifecycle product rendering Product Visualization Productivity productivity and workflow efficiency. productivity tips productivity tools Professional 3D design Professional CAD Professional Drawings professional printing Professional Tips Professional Workflow progress management Project Accuracy project automation Project Collaboration project consistency Project Coordination project dashboard Project Documentation project efficiency Project Goals project management Project Management Tools project milestones Project Monitoring project organization Project Oversight project planning Project Progress project quality project timeline project tracking Project Visualization project workflow PTC Creo Publish Drawings PURGE PURGE AutoCAD Rail Transit Rapid Prototyping Realism realistic rendering realistic scenes ReCap Redshift Shader reduce CAD errors reduce CAD file size Reduce Errors reduce manual updates Reducing redundancy Redundant Work Render Render Optimization Render Passes Render Quality Render Settings render tips Rendering rendering engine Rendering Engines Rendering Optimization rendering settings rendering software Rendering Techniques Rendering Tips Rendering Workflow RenderMan Renewable Energy Renovation Project Renovation Workflow repetition-free workflow repetitive drawing Repetitive Elements repetitive-free Reports Resizable Block restoration workflow Reusable Components Revision Control Revision Tracking Revit Revit add-ins Revit API Revit automation Revit Best Practices Revit Collaboration Revit Documentation Revit Family Revit integration Revit MEP Revit Performance Revit Phasing Revit plugin Revit Plugins Revit Scripting Revit skills Revit Standards Revit Strategies Revit Structure Revit Tags Revit Template Revit templates Revit Tips Revit tutorial Revit Workflow Ribbon Rigging Rigid Body robotics ROI Room planning save hours of work Save Time save time CAD Scale Autodesk Schedules screen Scripts Sculpting Secure Collaboration Sensor Data Shader Networks sheet management Sheet Metal Sheet Metal Design Sheet Metal Tricks Sheet organization sheet set Sheet Set Automation Sheet Set Efficiency Sheet Set fields Sheet Set Management Sheet Set Manager Sheet Set Optimization Sheet Set Organization Sheet Set Software Sheet Set Standards Sheet Set Tips Sheet Set Tools Sheet Sets sheet sets workflow Sheets shortcut keys Shortcuts Siemens NX Simulation simulation tools Sketch Sketching Tricks Small Firms Smart Architecture Smart Block Smart Building Design Smart CAD smart CAD tools Smart City Smart Design smart dimensioning Smart Engineering Smart Factory Smart Infrastructur Smart Project Smart Sheet Management Smart Sheet Set Tools Smart Sheet Sets Smart Workflows Smoke Soft Body Software Compliance software ecosystem Software Management Software Trends software troubleshooting Software Update Solar Energy Solar Panels SolidWorks Space planning standard part libraries Standardization Standardize standardized templates Startup Design static stress STB Steel Structure Design Stress-Free Structural Design Structural Modeling Structural Optimization subscription model Subscription Value surface finish Surface Modeling sustainability sustainable design Sustainable Manufacturing system performance T-Spline task management team collaboration Team Efficiency Team Productivity Team Projects team training guide technical documentation Technical Drawing technical support Template management Template Setup Template usage templates text settings text style Texture Mapping Texturing thermal analysis time efficiency Time Management time saving tools time savings time-saving time-saving tools Title Block title block automation Title Blocks Tool Libraries Tool Management Tool Palette Guide toolbar toolpath Toolpath Optimization Toolpaths Topography Track Track changes Troubleshooting Tutorial Tutorials Unfolding Techniques urban planning User Interface (UI) UV Mapping UV Unwrap V-Ray Vault Best Practices Vault Lifecycle Vault Mistakes Vector Plotting vehicle modeling version control VFX View Filters Viewport configuration viewports Virtual Environments virtual reality visual effects visualization workflow VR VR Tools VRED Water Infrastructure Water Management Weight Painting What’s New in Autodesk Wind Energy Wind Turbines Workbook workflow Workflow Automation workflow efficiency Workflow Optimization Workflow Tips Worksets Worksharing Workspace XLS Xref Xrefs เขียนแบบ