| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Linq; |
| | 4 | | using System.Text; |
| | 5 | | using System.Text.RegularExpressions; |
| | 6 | | using DCL.Models; |
| | 7 | | using UnityEngine; |
| | 8 | |
|
| | 9 | | namespace DCL |
| | 10 | | { |
| | 11 | | public class EntityIdHelper |
| | 12 | | { |
| 769 | 13 | | internal Dictionary<long, string> entityIdToLegacyId = new Dictionary<long, string>(); |
| 769 | 14 | | internal Dictionary<string, long> invalidEntities = new Dictionary<string, long>(); |
| | 15 | |
|
| 769 | 16 | | private int invalidEntityCounter = -1; |
| | 17 | |
|
| | 18 | | public string GetOriginalId(long entityId) |
| | 19 | | { |
| | 20 | | // if the entityId is less than 512, then it is considered well-known. |
| 47 | 21 | | if (entityId < 512) |
| | 22 | | { |
| | 23 | | switch (entityId) |
| | 24 | | { |
| | 25 | | case (long)SpecialEntityId.SCENE_ROOT_ENTITY: |
| 1 | 26 | | return SpecialEntityIdLegacyLiteral.SCENE_ROOT_ENTITY; |
| | 27 | |
|
| | 28 | | case (long)SpecialEntityId.FIRST_PERSON_CAMERA_ENTITY_REFERENCE: |
| 1 | 29 | | return SpecialEntityIdLegacyLiteral.FIRST_PERSON_CAMERA_ENTITY_REFERENCE; |
| | 30 | |
|
| | 31 | | case (long)SpecialEntityId.AVATAR_ENTITY_REFERENCE: |
| 1 | 32 | | return SpecialEntityIdLegacyLiteral.AVATAR_ENTITY_REFERENCE; |
| | 33 | |
|
| | 34 | | case (long)SpecialEntityId.AVATAR_POSITION_REFERENCE: |
| 1 | 35 | | return SpecialEntityIdLegacyLiteral.AVATAR_POSITION_REFERENCE; |
| | 36 | |
|
| | 37 | | case (long)SpecialEntityId.THIRD_PERSON_CAMERA_ENTITY_REFERENCE: |
| 1 | 38 | | return SpecialEntityIdLegacyLiteral.THIRD_PERSON_CAMERA_ENTITY_REFERENCE; |
| | 39 | | } |
| | 40 | | } |
| | 41 | |
|
| | 42 | | // if it has the 0x8000_0000 mask, then it *should* be stored in the entityIdToLegacyId map |
| 42 | 43 | | if ((entityId & 0x80000000) != 0 && entityIdToLegacyId.ContainsKey(entityId)) |
| | 44 | | { |
| | 45 | | // the entity has the 0x80000000 mask signaling it was an invalid ID |
| 2 | 46 | | return entityIdToLegacyId[entityId]; |
| | 47 | | } |
| | 48 | |
|
| | 49 | | // lastly, fallback to generating the entityId based on the number. |
| 40 | 50 | | return ToBase36EntityId(entityId); |
| | 51 | | } |
| | 52 | |
|
| | 53 | | private string ToBase36EntityId(long value) |
| | 54 | | { |
| | 55 | | // TODO(mendez): the string builder approach can be better. It does use more allocations |
| | 56 | | // than the optimal. The ideal scenario would use a `stackalloc char[13]` instead |
| | 57 | | // of StringBuilder but my knowledge and tools for C# are limited at the moment |
| | 58 | | const string base36 = "0123456789abcdefghijklmnopqrstuvwxyz"; |
| 40 | 59 | | var sb = new StringBuilder(13); |
| | 60 | |
|
| 40 | 61 | | value = value >> 9; // recover from bit-shift |
| 40 | 62 | | value -= 1; // recover the added 1 |
| | 63 | |
|
| | 64 | | do |
| | 65 | | { |
| 58 | 66 | | sb.Insert(0, base36[(byte)(value % 36)]); |
| 58 | 67 | | value /= 36; |
| 58 | 68 | | } while (value != 0); |
| | 69 | |
|
| | 70 | | // E prefix for entities |
| 40 | 71 | | sb.Insert(0, "E"); |
| | 72 | |
|
| 40 | 73 | | return sb.ToString(); |
| | 74 | | } |
| | 75 | |
|
| | 76 | | public long EntityFromLegacyEntityString(string entityId) |
| | 77 | | { |
| 84 | 78 | | long entityIdLong = GetConvertedEntityId(entityId); |
| 84 | 79 | | return entityIdLong; |
| | 80 | | } |
| | 81 | |
|
| | 82 | | private long GetConvertedEntityId(string entityId) |
| | 83 | | { |
| | 84 | | // entity Ids in base36 start with E |
| 84 | 85 | | if (entityId[0] == 'E') |
| | 86 | | { |
| 31 | 87 | | long result = 1; |
| 31 | 88 | | long power = 1; |
| 192 | 89 | | for (var i = entityId.Length - 1; i > 0; i--) |
| | 90 | | { |
| 67 | 91 | | char charCode = entityId[i]; |
| 67 | 92 | | bool isNumber = charCode >= '0' && charCode <= '9'; |
| 67 | 93 | | bool isBase36Letter = charCode >= 'a' && charCode <= 'z'; |
| 67 | 94 | | if (isNumber) |
| 21 | 95 | | result += (charCode - '0') * power; |
| 46 | 96 | | else if (isBase36Letter) |
| 44 | 97 | | result += (10 + (charCode - 'a')) * power; |
| | 98 | | else // non base36, fallback to entityIdFromDictionary |
| 2 | 99 | | return entityIdFromDictionary(entityId); |
| | 100 | |
|
| 65 | 101 | | power *= 36; |
| | 102 | | } |
| | 103 | |
|
| | 104 | | // reserve 512 entity ids (<<9) |
| 29 | 105 | | long newEntityIdLong = result << 9; |
| | 106 | |
|
| 29 | 107 | | return newEntityIdLong; |
| | 108 | | } |
| | 109 | |
|
| | 110 | | // non standard entity, fallback to entityIdFromDictionary |
| 53 | 111 | | return entityIdFromDictionary(entityId); |
| | 112 | | } |
| | 113 | |
|
| | 114 | | private long entityIdFromDictionary(string entityId) |
| | 115 | | { |
| | 116 | | // first we try against well known and lesser used entities |
| | 117 | | switch (entityId) |
| | 118 | | { |
| | 119 | | case SpecialEntityIdLegacyLiteral.SCENE_ROOT_ENTITY: |
| 2 | 120 | | return (long) SpecialEntityId.SCENE_ROOT_ENTITY; |
| | 121 | |
|
| | 122 | | case SpecialEntityIdLegacyLiteral.FIRST_PERSON_CAMERA_ENTITY_REFERENCE: |
| 2 | 123 | | return (long) SpecialEntityId.FIRST_PERSON_CAMERA_ENTITY_REFERENCE; |
| | 124 | |
|
| | 125 | | case SpecialEntityIdLegacyLiteral.AVATAR_ENTITY_REFERENCE: |
| 2 | 126 | | return (long) SpecialEntityId.AVATAR_ENTITY_REFERENCE; |
| | 127 | |
|
| | 128 | | case SpecialEntityIdLegacyLiteral.AVATAR_POSITION_REFERENCE: |
| 2 | 129 | | return (long) SpecialEntityId.AVATAR_POSITION_REFERENCE; |
| | 130 | |
|
| | 131 | | case SpecialEntityIdLegacyLiteral.THIRD_PERSON_CAMERA_ENTITY_REFERENCE: |
| 2 | 132 | | return (long) SpecialEntityId.THIRD_PERSON_CAMERA_ENTITY_REFERENCE; |
| | 133 | | } |
| | 134 | |
|
| | 135 | | // secondly, test if it was already seen in invalidEntities |
| 45 | 136 | | if (invalidEntities.ContainsKey(entityId)) |
| | 137 | | { |
| 2 | 138 | | return invalidEntities[entityId]; |
| | 139 | | } |
| | 140 | | else |
| | 141 | | { |
| | 142 | | // generate the new ID, uses the mask 0x8000_0000 |
| 43 | 143 | | var newEntityIdLong = ++invalidEntityCounter | 0x80000000; |
| | 144 | |
|
| | 145 | | // store the mapping from newEntityIdLong->original |
| 43 | 146 | | if (!entityIdToLegacyId.ContainsKey(newEntityIdLong)) |
| 43 | 147 | | entityIdToLegacyId[newEntityIdLong] = entityId; |
| | 148 | |
|
| | 149 | | // store the mapping original->newEntityIdLong |
| 43 | 150 | | invalidEntities.Add(entityId, newEntityIdLong); |
| | 151 | |
|
| 43 | 152 | | return newEntityIdLong; |
| | 153 | | } |
| | 154 | | } |
| | 155 | | } |
| | 156 | | } |