Bug 1855375: Basic implementation for Yelp Suggestions r=fluent-reviewers,flod,adw
[gecko.git] / intl / lwbrk / nsComplexBreaker.cpp
blob0c0a5e45b6a705ea779058fd978a8bdbd53bc007
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsComplexBreaker.h"
8 #include <algorithm>
10 #include "MainThreadUtils.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/Services.h"
13 #include "mozilla/UniquePtr.h"
14 #include "nsTHashMap.h"
15 #include "nsIObserver.h"
16 #include "nsIObserverService.h"
17 #include "nsString.h"
18 #include "nsTArray.h"
19 #include "nsThreadUtils.h"
21 using namespace mozilla;
23 using CacheMap = nsTHashMap<nsString, nsTArray<uint8_t>>;
25 static UniquePtr<CacheMap> sBreakCache;
27 // The underlying hash table extends capacity, when it hits .75 full and uses
28 // powers of 2 for sizing. This cache limit will hopefully mean most pages fit
29 // within the cache, while keeping it to a reasonable size. Also by holding the
30 // previous cache even if pages are bigger than the cache the most commonly used
31 // should still remain fast.
32 static const int kCacheLimit = 3072;
34 static UniquePtr<CacheMap> sOldBreakCache;
36 // Simple runnable to delete caches off the main thread.
37 class CacheDeleter final : public Runnable {
38 public:
39 explicit CacheDeleter(UniquePtr<CacheMap> aCacheToDelete)
40 : Runnable("ComplexBreaker CacheDeleter"),
41 mCacheToDelete(std::move(aCacheToDelete)) {}
43 NS_IMETHOD Run() override {
44 MOZ_ASSERT(!NS_IsMainThread());
45 mCacheToDelete = nullptr;
46 return NS_OK;
49 private:
50 UniquePtr<CacheMap> mCacheToDelete;
53 class ComplexBreakObserver final : public nsIObserver {
54 ~ComplexBreakObserver() = default;
56 public:
57 NS_DECL_ISUPPORTS
58 NS_DECL_NSIOBSERVER
61 NS_IMPL_ISUPPORTS(ComplexBreakObserver, nsIObserver)
63 NS_IMETHODIMP ComplexBreakObserver::Observe(nsISupports* aSubject,
64 const char* aTopic,
65 const char16_t* aData) {
66 MOZ_ASSERT(NS_IsMainThread());
68 if (strcmp(aTopic, "memory-pressure") == 0) {
69 if (sOldBreakCache) {
70 // We have an old cache, so delete that one first.
71 NS_DispatchBackgroundTask(
72 MakeAndAddRef<CacheDeleter>(std::move(sOldBreakCache)));
73 } else if (sBreakCache) {
74 NS_DispatchBackgroundTask(
75 MakeAndAddRef<CacheDeleter>(std::move(sBreakCache)));
79 return NS_OK;
82 void ComplexBreaker::Initialize() {
83 MOZ_ASSERT(NS_IsMainThread());
85 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
86 if (obs) {
87 obs->AddObserver(new ComplexBreakObserver(), "memory-pressure", false);
91 void ComplexBreaker::Shutdown() {
92 MOZ_ASSERT(NS_IsMainThread());
94 sBreakCache = nullptr;
95 sOldBreakCache = nullptr;
98 static void AddToCache(const char16_t* aText, uint32_t aLength,
99 nsTArray<uint8_t> aBreakBefore) {
100 if (NS_WARN_IF(!sBreakCache->InsertOrUpdate(
101 nsString(aText, aLength), std::move(aBreakBefore), fallible))) {
102 return;
105 if (sBreakCache->Count() <= kCacheLimit) {
106 return;
109 if (sOldBreakCache) {
110 NS_DispatchBackgroundTask(
111 MakeAndAddRef<CacheDeleter>(std::move(sOldBreakCache)));
114 sOldBreakCache = std::move(sBreakCache);
117 static void CopyAndFill(const nsTArray<uint8_t>& aCachedBreakBefore,
118 uint8_t* aBreakBefore, uint8_t* aEndBreakBefore) {
119 auto* startFill = std::copy(aCachedBreakBefore.begin(),
120 aCachedBreakBefore.end(), aBreakBefore);
121 std::fill(startFill, aEndBreakBefore, false);
124 void ComplexBreaker::GetBreaks(const char16_t* aText, uint32_t aLength,
125 uint8_t* aBreakBefore) {
126 // It is believed that this is only called on the main thread, so we don't
127 // need to lock the caching structures. A diagnostic assert is used in case
128 // our tests don't exercise all code paths.
129 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
131 MOZ_ASSERT(aText, "aText shouldn't be null");
132 MOZ_ASSERT(aLength, "aLength shouldn't be zero");
133 MOZ_ASSERT(aBreakBefore, "aBreakBefore shouldn't be null");
135 // If we have a cache then check it, if not then create it.
136 if (sBreakCache) {
137 if (auto entry =
138 sBreakCache->Lookup(nsDependentSubstring(aText, aLength))) {
139 auto& breakBefore = entry.Data();
140 CopyAndFill(breakBefore, aBreakBefore, aBreakBefore + aLength);
141 return;
143 } else {
144 sBreakCache = MakeUnique<CacheMap>();
147 // We keep the previous cache when we hit our limit, so that the most recently
148 // used fragments remain fast.
149 if (sOldBreakCache) {
150 auto breakBefore =
151 sOldBreakCache->Extract(nsDependentSubstring(aText, aLength));
152 if (breakBefore) {
153 CopyAndFill(*breakBefore, aBreakBefore, aBreakBefore + aLength);
154 // Move the entry to the latest cache.
155 AddToCache(aText, aLength, std::move(*breakBefore));
156 return;
160 NS_GetComplexLineBreaks(aText, aLength, aBreakBefore);
162 // As a very simple memory saving measure we trim off trailing elements that
163 // are false before caching.
164 auto* afterLastTrue = aBreakBefore + aLength;
165 while (!*(afterLastTrue - 1)) {
166 if (--afterLastTrue == aBreakBefore) {
167 break;
171 AddToCache(aText, aLength,
172 nsTArray<uint8_t>(aBreakBefore, afterLastTrue - aBreakBefore));