Home | History | Annotate | Download | only in dicnode
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #ifndef LATINIME_DIC_NODE_H
     18 #define LATINIME_DIC_NODE_H
     19 
     20 #include "defines.h"
     21 #include "suggest/core/dicnode/dic_node_profiler.h"
     22 #include "suggest/core/dicnode/dic_node_release_listener.h"
     23 #include "suggest/core/dicnode/internal/dic_node_state.h"
     24 #include "suggest/core/dicnode/internal/dic_node_properties.h"
     25 #include "suggest/core/dictionary/digraph_utils.h"
     26 #include "utils/char_utils.h"
     27 
     28 #if DEBUG_DICT
     29 #define LOGI_SHOW_ADD_COST_PROP \
     30         do { char charBuf[50]; \
     31         INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
     32         AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \
     33                 __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \
     34                 getInputIndex(0), getNormalizedCompoundDistance(), charBuf); } while (0)
     35 #define DUMP_WORD_AND_SCORE(header) \
     36         do { char charBuf[50]; char prevWordCharBuf[50]; \
     37         INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
     38         INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.mPrevWord, \
     39                 mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf, \
     40                 NELEMS(prevWordCharBuf)); \
     41         AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d, %5f,", header, \
     42                 getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \
     43                 getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \
     44                 getInputIndex(0), getNormalizedCompoundDistanceAfterFirstWord()); \
     45         } while (0)
     46 #else
     47 #define LOGI_SHOW_ADD_COST_PROP
     48 #define DUMP_WORD_AND_SCORE(header)
     49 #endif
     50 
     51 namespace latinime {
     52 
     53 // This struct is purely a bucket to return values. No instances of this struct should be kept.
     54 struct DicNode_InputStateG {
     55     DicNode_InputStateG()
     56             : mNeedsToUpdateInputStateG(false), mPointerId(0), mInputIndex(0),
     57               mPrevCodePoint(0), mTerminalDiffCost(0.0f), mRawLength(0.0f),
     58               mDoubleLetterLevel(NOT_A_DOUBLE_LETTER) {}
     59 
     60     bool mNeedsToUpdateInputStateG;
     61     int mPointerId;
     62     int16_t mInputIndex;
     63     int mPrevCodePoint;
     64     float mTerminalDiffCost;
     65     float mRawLength;
     66     DoubleLetterLevel mDoubleLetterLevel;
     67 };
     68 
     69 class DicNode {
     70     // Caveat: We define Weighting as a friend class of DicNode to let Weighting change
     71     // the distance of DicNode.
     72     // Caution!!! In general, we avoid using the "friend" access modifier.
     73     // This is an exception to explicitly hide DicNode::addCost() from all classes but Weighting.
     74     friend class Weighting;
     75 
     76  public:
     77 #if DEBUG_DICT
     78     DicNodeProfiler mProfiler;
     79 #endif
     80     //////////////////
     81     // Memory utils //
     82     //////////////////
     83     AK_FORCE_INLINE static void managedDelete(DicNode *node) {
     84         node->remove();
     85     }
     86     // end
     87     /////////////////
     88 
     89     AK_FORCE_INLINE DicNode()
     90             :
     91 #if DEBUG_DICT
     92               mProfiler(),
     93 #endif
     94               mDicNodeProperties(), mDicNodeState(), mIsCachedForNextSuggestion(false),
     95               mIsUsed(false), mReleaseListener(0) {}
     96 
     97     DicNode(const DicNode &dicNode);
     98     DicNode &operator=(const DicNode &dicNode);
     99     virtual ~DicNode() {}
    100 
    101     // Init for copy
    102     void initByCopy(const DicNode *dicNode) {
    103         mIsUsed = true;
    104         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
    105         mDicNodeProperties.init(&dicNode->mDicNodeProperties);
    106         mDicNodeState.init(&dicNode->mDicNodeState);
    107         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
    108     }
    109 
    110     // Init for root with prevWordNodePos which is used for bigram
    111     void initAsRoot(const int rootGroupPos, const int prevWordNodePos) {
    112         mIsUsed = true;
    113         mIsCachedForNextSuggestion = false;
    114         mDicNodeProperties.init(
    115                 NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
    116                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
    117                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
    118                 0 /* terminalDepth */);
    119         mDicNodeState.init(prevWordNodePos);
    120         PROF_NODE_RESET(mProfiler);
    121     }
    122 
    123     // Init for root with previous word
    124     void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
    125         mIsUsed = true;
    126         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
    127         mDicNodeProperties.init(
    128                 NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
    129                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
    130                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
    131                 0 /* terminalDepth */);
    132         // TODO: Move to dicNodeState?
    133         mDicNodeState.mDicNodeStateOutput.init(); // reset for next word
    134         mDicNodeState.mDicNodeStateInput.init(
    135                 &dicNode->mDicNodeState.mDicNodeStateInput, true /* resetTerminalDiffCost */);
    136         mDicNodeState.mDicNodeStateScoring.init(
    137                 &dicNode->mDicNodeState.mDicNodeStateScoring);
    138         mDicNodeState.mDicNodeStatePrevWord.init(
    139                 dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1,
    140                 dicNode->mDicNodeProperties.getProbability(),
    141                 dicNode->mDicNodeProperties.getPos(),
    142                 dicNode->mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
    143                 dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(),
    144                 dicNode->getOutputWordBuf(),
    145                 dicNode->mDicNodeProperties.getDepth(),
    146                 dicNode->mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex(),
    147                 mDicNodeState.mDicNodeStateInput.getInputIndex(0) /* lastInputIndex */);
    148         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
    149     }
    150 
    151     void initAsPassingChild(DicNode *parentNode) {
    152         mIsUsed = true;
    153         mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
    154         const int c = parentNode->getNodeTypedCodePoint();
    155         mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
    156         mDicNodeState.init(&parentNode->mDicNodeState);
    157         PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
    158     }
    159 
    160     void initAsChild(const DicNode *const dicNode, const int pos, const int childrenPos,
    161             const int probability, const bool isTerminal, const bool hasChildren,
    162             const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
    163             const int *const mergedNodeCodePoints) {
    164         mIsUsed = true;
    165         uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
    166         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
    167         const uint16_t newLeavingDepth = static_cast<uint16_t>(
    168                 dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
    169         mDicNodeProperties.init(pos, childrenPos, mergedNodeCodePoints[0], probability,
    170                 isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth, newLeavingDepth);
    171         mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
    172                 mergedNodeCodePoints);
    173         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
    174     }
    175 
    176     AK_FORCE_INLINE void remove() {
    177         mIsUsed = false;
    178         if (mReleaseListener) {
    179             mReleaseListener->onReleased(this);
    180         }
    181     }
    182 
    183     bool isUsed() const {
    184         return mIsUsed;
    185     }
    186 
    187     bool isRoot() const {
    188         return getNodeCodePointCount() == 0;
    189     }
    190 
    191     bool hasChildren() const {
    192         return mDicNodeProperties.hasChildren();
    193     }
    194 
    195     bool isLeavingNode() const {
    196         ASSERT(getNodeCodePointCount() <= mDicNodeProperties.getLeavingDepth());
    197         return getNodeCodePointCount() == mDicNodeProperties.getLeavingDepth();
    198     }
    199 
    200     AK_FORCE_INLINE bool isFirstLetter() const {
    201         return getNodeCodePointCount() == 1;
    202     }
    203 
    204     bool isCached() const {
    205         return mIsCachedForNextSuggestion;
    206     }
    207 
    208     void setCached() {
    209         mIsCachedForNextSuggestion = true;
    210     }
    211 
    212     // Used to expand the node in DicNodeUtils
    213     int getNodeTypedCodePoint() const {
    214         return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getNodeCodePointCount());
    215     }
    216 
    217     // Check if the current word and the previous word can be considered as a valid multiple word
    218     // suggestion.
    219     bool isValidMultipleWordSuggestion() const {
    220         if (isBlacklistedOrNotAWord()) {
    221             return false;
    222         }
    223         // Treat suggestion as invalid if the current and the previous word are single character
    224         // words.
    225         const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
    226                 - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
    227         const int currentWordLen = getNodeCodePointCount();
    228         return (prevWordLen != 1 || currentWordLen != 1);
    229     }
    230 
    231     bool isFirstCharUppercase() const {
    232         const int c = getOutputWordBuf()[0];
    233         return CharUtils::isAsciiUpper(c);
    234     }
    235 
    236     bool isFirstWord() const {
    237         return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_DICT_POS;
    238     }
    239 
    240     bool isCompletion(const int inputSize) const {
    241         return mDicNodeState.mDicNodeStateInput.getInputIndex(0) >= inputSize;
    242     }
    243 
    244     bool canDoLookAheadCorrection(const int inputSize) const {
    245         return mDicNodeState.mDicNodeStateInput.getInputIndex(0) < inputSize - 1;
    246     }
    247 
    248     // Used to get bigram probability in DicNodeUtils
    249     int getPos() const {
    250         return mDicNodeProperties.getPos();
    251     }
    252 
    253     // Used to get bigram probability in DicNodeUtils
    254     int getPrevWordPos() const {
    255         return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
    256     }
    257 
    258     // Used in DicNodeUtils
    259     int getChildrenPos() const {
    260         return mDicNodeProperties.getChildrenPos();
    261     }
    262 
    263     int getProbability() const {
    264         return mDicNodeProperties.getProbability();
    265     }
    266 
    267     AK_FORCE_INLINE bool isTerminalWordNode() const {
    268         const bool isTerminalNodes = mDicNodeProperties.isTerminal();
    269         const int currentNodeDepth = getNodeCodePointCount();
    270         const int terminalNodeDepth = mDicNodeProperties.getLeavingDepth();
    271         return isTerminalNodes && currentNodeDepth > 0 && currentNodeDepth == terminalNodeDepth;
    272     }
    273 
    274     bool shouldBeFilteredBySafetyNetForBigram() const {
    275         const uint16_t currentDepth = getNodeCodePointCount();
    276         const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
    277                 - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
    278         return !(currentDepth > 0 && (currentDepth != 1 || prevWordLen != 1));
    279     }
    280 
    281     bool isTotalInputSizeExceedingLimit() const {
    282         const int prevWordsLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
    283         const int currentWordDepth = getNodeCodePointCount();
    284         // TODO: 3 can be 2? Needs to be investigated.
    285         // TODO: Have a const variable for 3 (or 2)
    286         return prevWordsLen + currentWordDepth > MAX_WORD_LENGTH - 3;
    287     }
    288 
    289     // TODO: This may be defective. Needs to be revised.
    290     bool truncateNode(const DicNode *const topNode, const int inputCommitPoint) {
    291         const int prevWordLenOfTop = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
    292         int newPrevWordStartIndex = inputCommitPoint;
    293         int charCount = 0;
    294         // Find new word start index
    295         for (int i = 0; i < prevWordLenOfTop; ++i) {
    296             const int c = mDicNodeState.mDicNodeStatePrevWord.getPrevWordCodePointAt(i);
    297             // TODO: Check other separators.
    298             if (c != KEYCODE_SPACE && c != KEYCODE_SINGLE_QUOTE) {
    299                 if (charCount == inputCommitPoint) {
    300                     newPrevWordStartIndex = i;
    301                     break;
    302                 }
    303                 ++charCount;
    304             }
    305         }
    306         if (!mDicNodeState.mDicNodeStatePrevWord.startsWith(
    307                 &topNode->mDicNodeState.mDicNodeStatePrevWord, newPrevWordStartIndex - 1)) {
    308             // Node mismatch.
    309             return false;
    310         }
    311         mDicNodeState.mDicNodeStateInput.truncate(inputCommitPoint);
    312         mDicNodeState.mDicNodeStatePrevWord.truncate(newPrevWordStartIndex);
    313         return true;
    314     }
    315 
    316     void outputResult(int *dest) const {
    317         const uint16_t prevWordLength = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
    318         const uint16_t currentDepth = getNodeCodePointCount();
    319         DicNodeUtils::appendTwoWords(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
    320                    prevWordLength, getOutputWordBuf(), currentDepth, dest);
    321         DUMP_WORD_AND_SCORE("OUTPUT");
    322     }
    323 
    324     // "Total" in this context (and other methods in this class) means the whole suggestion. When
    325     // this represents a multi-word suggestion, the referenced PtNode (in mDicNodeState) is only
    326     // the one that corresponds to the last word of the suggestion, and all the previous words
    327     // are concatenated together in mPrevWord - which contains a space at the end.
    328     int getTotalNodeSpaceCount() const {
    329         if (isFirstWord()) return 0;
    330         return CharUtils::getSpaceCount(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
    331                 mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength());
    332     }
    333 
    334     int getSecondWordFirstInputIndex(const ProximityInfoState *const pInfoState) const {
    335         const int inputIndex = mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex();
    336         if (inputIndex == NOT_AN_INDEX) {
    337             return NOT_AN_INDEX;
    338         } else {
    339             return pInfoState->getInputIndexOfSampledPoint(inputIndex);
    340         }
    341     }
    342 
    343     bool hasMultipleWords() const {
    344         return mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() > 0;
    345     }
    346 
    347     int getProximityCorrectionCount() const {
    348         return mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount();
    349     }
    350 
    351     int getEditCorrectionCount() const {
    352         return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount();
    353     }
    354 
    355     // Used to prune nodes
    356     float getNormalizedCompoundDistance() const {
    357         return mDicNodeState.mDicNodeStateScoring.getNormalizedCompoundDistance();
    358     }
    359 
    360     // Used to prune nodes
    361     float getNormalizedSpatialDistance() const {
    362         return mDicNodeState.mDicNodeStateScoring.getSpatialDistance()
    363                 / static_cast<float>(getInputIndex(0) + 1);
    364     }
    365 
    366     // Used to prune nodes
    367     float getCompoundDistance() const {
    368         return mDicNodeState.mDicNodeStateScoring.getCompoundDistance();
    369     }
    370 
    371     // Used to prune nodes
    372     float getCompoundDistance(const float languageWeight) const {
    373         return mDicNodeState.mDicNodeStateScoring.getCompoundDistance(languageWeight);
    374     }
    375 
    376     // Used to commit input partially
    377     int getPrevWordNodePos() const {
    378         return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
    379     }
    380 
    381     AK_FORCE_INLINE const int *getOutputWordBuf() const {
    382         return mDicNodeState.mDicNodeStateOutput.mCodePointsBuf;
    383     }
    384 
    385     int getPrevCodePointG(int pointerId) const {
    386         return mDicNodeState.mDicNodeStateInput.getPrevCodePoint(pointerId);
    387     }
    388 
    389     // Whether the current codepoint can be an intentional omission, in which case the traversal
    390     // algorithm will always check for a possible omission here.
    391     bool canBeIntentionalOmission() const {
    392         return CharUtils::isIntentionalOmissionCodePoint(getNodeCodePoint());
    393     }
    394 
    395     // Whether the omission is so frequent that it should incur zero cost.
    396     bool isZeroCostOmission() const {
    397         // TODO: do not hardcode and read from header
    398         return (getNodeCodePoint() == KEYCODE_SINGLE_QUOTE);
    399     }
    400 
    401     // TODO: remove
    402     float getTerminalDiffCostG(int path) const {
    403         return mDicNodeState.mDicNodeStateInput.getTerminalDiffCost(path);
    404     }
    405 
    406     //////////////////////
    407     // Temporary getter //
    408     // TODO: Remove     //
    409     //////////////////////
    410     // TODO: Remove once touch path is merged into ProximityInfoState
    411     // Note: Returned codepoint may be a digraph codepoint if the node is in a composite glyph.
    412     int getNodeCodePoint() const {
    413         const int codePoint = mDicNodeProperties.getNodeCodePoint();
    414         const DigraphUtils::DigraphCodePointIndex digraphIndex =
    415                 mDicNodeState.mDicNodeStateScoring.getDigraphIndex();
    416         if (digraphIndex == DigraphUtils::NOT_A_DIGRAPH_INDEX) {
    417             return codePoint;
    418         }
    419         return DigraphUtils::getDigraphCodePointForIndex(codePoint, digraphIndex);
    420     }
    421 
    422     ////////////////////////////////
    423     // Utils for cost calculation //
    424     ////////////////////////////////
    425     AK_FORCE_INLINE bool isSameNodeCodePoint(const DicNode *const dicNode) const {
    426         return mDicNodeProperties.getNodeCodePoint()
    427                 == dicNode->mDicNodeProperties.getNodeCodePoint();
    428     }
    429 
    430     // TODO: remove
    431     // TODO: rename getNextInputIndex
    432     int16_t getInputIndex(int pointerId) const {
    433         return mDicNodeState.mDicNodeStateInput.getInputIndex(pointerId);
    434     }
    435 
    436     ////////////////////////////////////
    437     // Getter of features for scoring //
    438     ////////////////////////////////////
    439     float getSpatialDistanceForScoring() const {
    440         return mDicNodeState.mDicNodeStateScoring.getSpatialDistance();
    441     }
    442 
    443     float getLanguageDistanceForScoring() const {
    444         return mDicNodeState.mDicNodeStateScoring.getLanguageDistance();
    445     }
    446 
    447     // For space-aware gestures, we store the normalized distance at the char index
    448     // that ends the first word of the suggestion. We call this the distance after
    449     // first word.
    450     float getNormalizedCompoundDistanceAfterFirstWord() const {
    451         return mDicNodeState.mDicNodeStateScoring.getNormalizedCompoundDistanceAfterFirstWord();
    452     }
    453 
    454     float getLanguageDistanceRatePerWordForScoring() const {
    455         const float langDist = getLanguageDistanceForScoring();
    456         const float totalWordCount =
    457                 static_cast<float>(mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1);
    458         return langDist / totalWordCount;
    459     }
    460 
    461     float getRawLength() const {
    462         return mDicNodeState.mDicNodeStateScoring.getRawLength();
    463     }
    464 
    465     bool isLessThanOneErrorForScoring() const {
    466         return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount()
    467                 + mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount() <= 1;
    468     }
    469 
    470     DoubleLetterLevel getDoubleLetterLevel() const {
    471         return mDicNodeState.mDicNodeStateScoring.getDoubleLetterLevel();
    472     }
    473 
    474     void setDoubleLetterLevel(DoubleLetterLevel doubleLetterLevel) {
    475         mDicNodeState.mDicNodeStateScoring.setDoubleLetterLevel(doubleLetterLevel);
    476     }
    477 
    478     bool isInDigraph() const {
    479         return mDicNodeState.mDicNodeStateScoring.getDigraphIndex()
    480                 != DigraphUtils::NOT_A_DIGRAPH_INDEX;
    481     }
    482 
    483     void advanceDigraphIndex() {
    484         mDicNodeState.mDicNodeStateScoring.advanceDigraphIndex();
    485     }
    486 
    487     bool isExactMatch() const {
    488         return mDicNodeState.mDicNodeStateScoring.isExactMatch();
    489     }
    490 
    491     bool isBlacklistedOrNotAWord() const {
    492         return mDicNodeProperties.isBlacklistedOrNotAWord();
    493     }
    494 
    495     inline uint16_t getNodeCodePointCount() const {
    496         return mDicNodeProperties.getDepth();
    497     }
    498 
    499     // Returns code point count including spaces
    500     inline uint16_t getTotalNodeCodePointCount() const {
    501         return getNodeCodePointCount() + mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
    502     }
    503 
    504     AK_FORCE_INLINE void dump(const char *tag) const {
    505 #if DEBUG_DICT
    506         DUMP_WORD_AND_SCORE(tag);
    507 #if DEBUG_DUMP_ERROR
    508         mProfiler.dump();
    509 #endif
    510 #endif
    511     }
    512 
    513     void setReleaseListener(DicNodeReleaseListener *releaseListener) {
    514         mReleaseListener = releaseListener;
    515     }
    516 
    517     AK_FORCE_INLINE bool compare(const DicNode *right) {
    518         if (!isUsed() && !right->isUsed()) {
    519             // Compare pointer values here for stable comparison
    520             return this > right;
    521         }
    522         if (!isUsed()) {
    523             return true;
    524         }
    525         if (!right->isUsed()) {
    526             return false;
    527         }
    528         // Promote exact matches to prevent them from being pruned.
    529         const bool leftExactMatch = isExactMatch();
    530         const bool rightExactMatch = right->isExactMatch();
    531         if (leftExactMatch != rightExactMatch) {
    532             return leftExactMatch;
    533         }
    534         const float diff =
    535                 right->getNormalizedCompoundDistance() - getNormalizedCompoundDistance();
    536         static const float MIN_DIFF = 0.000001f;
    537         if (diff > MIN_DIFF) {
    538             return true;
    539         } else if (diff < -MIN_DIFF) {
    540             return false;
    541         }
    542         const int depth = getNodeCodePointCount();
    543         const int depthDiff = right->getNodeCodePointCount() - depth;
    544         if (depthDiff != 0) {
    545             return depthDiff > 0;
    546         }
    547         for (int i = 0; i < depth; ++i) {
    548             const int codePoint = mDicNodeState.mDicNodeStateOutput.getCodePointAt(i);
    549             const int rightCodePoint = right->mDicNodeState.mDicNodeStateOutput.getCodePointAt(i);
    550             if (codePoint != rightCodePoint) {
    551                 return rightCodePoint > codePoint;
    552             }
    553         }
    554         // Compare pointer values here for stable comparison
    555         return this > right;
    556     }
    557 
    558  private:
    559     DicNodeProperties mDicNodeProperties;
    560     DicNodeState mDicNodeState;
    561     // TODO: Remove
    562     bool mIsCachedForNextSuggestion;
    563     bool mIsUsed;
    564     DicNodeReleaseListener *mReleaseListener;
    565 
    566     AK_FORCE_INLINE int getTotalInputIndex() const {
    567         int index = 0;
    568         for (int i = 0; i < MAX_POINTER_COUNT_G; i++) {
    569             index += mDicNodeState.mDicNodeStateInput.getInputIndex(i);
    570         }
    571         return index;
    572     }
    573 
    574     // Caveat: Must not be called outside Weighting
    575     // This restriction is guaranteed by "friend"
    576     AK_FORCE_INLINE void addCost(const float spatialCost, const float languageCost,
    577             const bool doNormalization, const int inputSize, const ErrorType errorType) {
    578         if (DEBUG_GEO_FULL) {
    579             LOGI_SHOW_ADD_COST_PROP;
    580         }
    581         mDicNodeState.mDicNodeStateScoring.addCost(spatialCost, languageCost, doNormalization,
    582                 inputSize, getTotalInputIndex(), errorType);
    583     }
    584 
    585     // Saves the current normalized compound distance for space-aware gestures.
    586     // See getNormalizedCompoundDistanceAfterFirstWord for details.
    587     AK_FORCE_INLINE void saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet() {
    588         mDicNodeState.mDicNodeStateScoring.saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet();
    589     }
    590 
    591     // Caveat: Must not be called outside Weighting
    592     // This restriction is guaranteed by "friend"
    593     AK_FORCE_INLINE void forwardInputIndex(const int pointerId, const int count,
    594             const bool overwritesPrevCodePointByNodeCodePoint) {
    595         if (count == 0) {
    596             return;
    597         }
    598         mDicNodeState.mDicNodeStateInput.forwardInputIndex(pointerId, count);
    599         if (overwritesPrevCodePointByNodeCodePoint) {
    600             mDicNodeState.mDicNodeStateInput.setPrevCodePoint(0, getNodeCodePoint());
    601         }
    602     }
    603 
    604     AK_FORCE_INLINE void updateInputIndexG(const DicNode_InputStateG *const inputStateG) {
    605         if (mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() == 1 && isFirstLetter()) {
    606             mDicNodeState.mDicNodeStatePrevWord.setSecondWordFirstInputIndex(
    607                     inputStateG->mInputIndex);
    608         }
    609         mDicNodeState.mDicNodeStateInput.updateInputIndexG(inputStateG->mPointerId,
    610                 inputStateG->mInputIndex, inputStateG->mPrevCodePoint,
    611                 inputStateG->mTerminalDiffCost, inputStateG->mRawLength);
    612         mDicNodeState.mDicNodeStateScoring.addRawLength(inputStateG->mRawLength);
    613         mDicNodeState.mDicNodeStateScoring.setDoubleLetterLevel(inputStateG->mDoubleLetterLevel);
    614     }
    615 };
    616 } // namespace latinime
    617 #endif // LATINIME_DIC_NODE_H
    618