Introduction
The term "asset" is used often, and has many different meanings in different industries and even in different areas of the same production facility. In TACTIC, an asset is an atomic entity with metadata and files associated with it. To avoid confusion, the TACTIC assets are called "searchable objects," shortened to sObjects.
sObjects
sObjects are the atomic entities (or assets) that TACTIC uses to manipulate data and check in files. An sObject can be any entity required in a production. Examples of sObjects include shots, textures, users, tasks, production notes, and so on.
Every sObject must belong to a search type, also known as sType. Search types are a set of unique string entities that serve to classify all variations of sObjects. Search types are registered in the "search_object" table in the "sthpw" database. This table defines the properties for each search type, and is used to ensure that sObjects adheres to their search type properties. For instance, in a custom project, you may have a custom/shot sType created for shot. Once it’s registered, you can add shot entries in the shot table that it generates. The shot entries are the shot sObjects.
It is technically possible to store data on assets anywhere, but the TACTIC approach is to use an SQL database so sObject data can be tracked in the database and rules can be enforced. In TACTIC, each sObject is represented as a table in the database. All sObjects for your project are stored in a project-wide database and cross-project sObjects (for example, those related to users) are stored in the main TACTIC database "sthpw."
The TACTIC architecture is an MVC architecture with the following major components:
SObject - Model(M) | Provides the data model. All interactions with the data model use sObjects and their derived classes. |
Widget - View(V) | Provides the display model, which determines the user interface and how users interact with the web application. The display architecture is built upon hierarchical widgets that are SObject-aware (that is, they use sObjects to define the interface). |
Command - Command© | Provides higher-level interactions with the data model. All actions affecting the data model or the filesystem must go through a command layer so that the changes can be tracked and completely undoable should something go wrong. |
Search | Provides a search model so widgets can obtain the SObjects they need to complete the interface display. Each type of sObject has a registered name which is used in the search engine to identify which sType to search. This provides a consistent interface to access all sObjects regardless of the location of the sObject in the database or table. |
In summary, widgets make use of the Search, get SObjects, and use commands to change persistent data. The sObject communication unit binds the view layer with the data model.
Main Data Objects
SObjects (searchable objects) are atomic, self-contained units that contain attributes. A particular sObject can be uniquely identified by two parameters: a search type and a search ID. Often these two parameters are combined into a "search key" defined as <search_type>|<search_id> (joined with the "|" character). Search keys allow you to uniquely identify any SObject using a single string.
Particular SObjects are obtained using the search engine, which generally returns a list of SObjects. The search engine is flexible enough to allow arbitrary bits of SQL code to be used for a search, although that approach is discouraged. (To maximize code reuse, it is better to put SQL code inside the low-level business objects that provide static functions to higher level parts of the framework.)
Widgets
Widgets are the atomic drawing units. Typically, widgets are SObject-aware and can perform and affect searches and draw SObjects. Widgets can contain children, and many function calls will traverse down to their children. For example, a widget can be assigned a search object. It will perform this search and pass the results to all of its children widgets, who will make use of the result as necessary.
One important widget function is the get_display() function, which draws widgets and can generate HTML. This function can be as simple as just drawing something that has nothing to do with sObject data, or can be a complicated function retrieving and displaying sObjects and all of their child sObjects.
Widgets determine how users interact with the web application. They have a number of useful properties that allow for the rapid development of web applications. For example, they can have a search assigned to them to locate and retrieve sObjects. They can typically perform actions across the search results, affecting multiple SObjects.
Widgets call events and listen to events, allowing for inter-widget communication. They interact with each other in the web application by registering events. For example, one widget, on initialization, may register itself as a listener for a named event. Another widget may call the named event upon an arbitrary action, at which point all widgets that are registered listeners for that event will be executed. This type of interaction allows for multiple actions to occur as a result of a user interaction, such as the click of a single button.
Checkin/checkout is the framework for filesystem interaction. All interaction within the checkin/checkout framework is done through the SObjects themselves so that they can determine their own checkin/checkout conditions and mechanisms. The checkin framework creates a snapshot SObject that is related to the original SObject through a search_id. It assigns a unique file ID for every transaction, and creates snapshot attributes for the SObjects.
Engineering requirements for a particular application must be gathered and translated into widgets, including definitions of the widgets' relationships to each other.
AJAX Widgets
TACTIC’s widget hierarchy falls naturally within the AJAX paradigm, where widgets are capable of redrawing themselves. Instead of refreshing the entire page, AJAX widgets actively gather the required information from the page and send only that information to the web server (as opposed to the entire contents of the page). The widget then processes the information and updates itself. This technique makes a much more interactive application because the web server only has to draw the individual widget element instead of the entire page. In addition to a faster and more interactive experience, AJAX widgets significantly reduce the overall load on the web server, making TACTIC far more scalable with the same resources.
TACTIC’s interface runs on top the the client API, therefore all interaction between the client and the server run on an XMLRPC layer resting on top of AJAX. This is very convenient for complex interactions between the client and the server.
Web Drawing Engine
This drawing engine is based on numerous interface platforms generally geared towards traditional application design. However, it has be adjusted to accommodate the unique web environment. A typical application would define a number of predefined widgets and assemble them in a hierarchical relationship.
Specialized widgets must be created to serve specific functions: for example, checkin/checkout widgets, download widgets, upload widgets, and navigation widgets.
Persistent Store
All metadata is stored in an industry-standard SQL database. The database tables and rows are clearly marked and readable, so it is easy to access the data directly. In today’s fast-changing environment, it is essential to be able to quickly read and understand the underlying data stored to be able to maintain proper support for diagnosing and fixing problems.
All data is accessed through sObject entities, which provide the object relational mappings to the database tables. In general, a single sObject is represented by a row in the table of a database. The table defines the type of SObjects stored in it, and there is usually a one-to-one relationship between the attributes of each sObject and the columns in the database.
Directory and File Naming Conventions
It is just as critical to be able to navigate the filesystem and understand what is located there. Therefore, advanced naming conventions are filtered through naming classes, which use clear procedures to create filenames based on metadata in the database. On the other hand, naming conventions can be driven by some expressions such as {sobject.code}_{snapshot.context}_v{snapshot.version}.{ext}.
Directories and file naming are handled slightly differently. TACTIC builds file names procedurally and then stores them in the database. On the other hand, TACTIC never stores directory names directly in the database, but always builds them up procedurally. This additional level of abstraction provides the opportunity to reorganize your asset structure as needed (because the directory structure isn’t hard-coded). Note that there may be other dependencies that are outside the control of TACTIC, so great care must be taken should you decide to reorganize the directory structure of your assets.
Outputting to the Debug_Log Table With The TacticServerStub.log() Function
The TACTIC Script Editor allows for Javascript and Python based scripts to be written and stored in a "custom script" sObject. These scripts harness the power of Javascript in the web browser along with the power of the Python TACTIC Client API. They can be structured to run on a general execution, by a trigger or, they can be attached to a button to execute for a specific sObject.
One of the main benefits with using this method of custom scripting in TACTIC is that the script writer does not have to have direct access to the server’s file system.
The TacticServerStub.log() method writes to the table named debug_log in the sthpw database.
The first parameter of the TacticServerStub.log() method is named level. The argument for level can be one of the following keywords:
level | critical |
error | warning |
info | debug - arbitrary debug level category |
The TacticServerStub.log() method can be used as follows:
var server = TacticServerStub.get() server.log('debug','My log message for the debug group.')
The debug level argument provides the convenience of grouping the Debug Log table by debug levels. This table can be found under:
Admin Views → Server → Debug Log
Note
These 5 debug levels are arbitrary.
The only purpose the levels serve are to group the messages when they are sorted in the table.
Outputting to the TACTIC Web Client Output Log With The log Methods
While writing scripts in the TACTIC Script Editor, messages can be output to the Web Client Output Log.
Below are the 5 Javascript methods in use. The most vocal method, log.critical(), is at the top:
Below is the Output Log console from above the sample script. It can be found under:
Main Gear menu → Tools → Web Client Output Log.
The level of the log messages which appear in the Javascript Output Client Log can be controlled. The level can be adjusted under: My Admin → User Preferences.
Below is a table to illustrate what the setting for each level will display
critical setting | only display messages that are from log.critical() |
error setting | only display messages that are from log.critical() or log.error() |
warning setting | only display messages that are from log.critical() or log.error() or log.warning() |
info setting | only display messages that are from log.critical() or log.error() or log.warning() or log.info() |
debug setting | only display messages that are from log.critical() or log.error() or log.warning() or log.info() or log.debug() |
For example, if the Web Client Logging Level is set in the preferences to the warning level, we will only see messages that are from log.warning(), log.error() and log.critical(). ie. Only messages at the same level or above that level will be displayed in the Web Client Output Log.
Client API JavaScript Samples
Example 1: Insert A New sObject
// INSERT A NEW SOBJECT var server = TacticServerStub.get(); var code = "truck"; var asset_name = "truck"; var description = "A model of a truck."; var search_type = "toy_factory/lego_set"; var project = "toy_factory"; var data = { 'code': code, 'name': asset_name, 'description': description }; var search_key = server.build_search_key(search_type, code, project); var result = server.insert(search_type, data); log.debug(result);
Results after insert:
Example 2: Get An sObject by Its Search Key
// GET BY SEARCH_KEY var server = TacticServerStub.get(); var search_type = "toyrus/lego_set"; var code = "model_crane"; var project = "toyrus"; var search_key = server.build_search_key(search_type, code, project); var result = server.get_by_search_key(search_key); alert(result.description); server.log("debug", result);
Results after get_by_search_key():
Example 3: Update An Existing sObject
// UPDATE EXISTING SOBJECT var server = TacticServerStub.get(); var code = "model_crane"; var project = "toyrus"; var asset_name = "model crane"; var description = "Revised description of a crane."; var search_type = "toyrus/lego_set"; var data = { 'code': code, 'name': asset_name, 'description': description }; var search_key = server.build_search_key(search_type, code, project); var result = server.update(search_key, data); server.log("debug", result);
Results after update:*Example 4: Retire An Existing sObject*
// RETIRE AN EXISTING OBJECT var server = TacticServerStub.get(); var search_type = "toyrus/lego_set"; var code = "model_crane"; var project = "toyrus"; var search_key = server.build_search_key(search_type, code, project); var results = server.retire_sobject(search_key); server.log("debug", result);
Results after retire: