Skip to content
This repository was archived by the owner on Mar 6, 2022. It is now read-only.

Commit 89eae53

Browse files
authored
Optimize workspace update (#17)
* Added benchmark for workspace update * Add a minimum delay for workspace updates * Fix CS * Do not use custom script location
1 parent e61f389 commit 89eae53

File tree

9 files changed

+326
-9
lines changed

9 files changed

+326
-9
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/tests/*/Workspace
55
/composer.lock
66
/.php_cs.cache
7+
/.phpbench

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"friendsofphp/php-cs-fixer": "^2.17",
3636
"jangregor/phpstan-prophecy": "^0.8.0",
3737
"phpactor/test-utils": "^1.1",
38-
"phpbench/phpbench": "^1.0.0-alpha3",
38+
"phpbench/phpbench": "^1.0.0",
3939
"phpspec/prophecy-phpunit": "^2.0",
4040
"phpstan/phpstan": "~0.12.0",
4141
"phpunit/phpunit": "^9.0",

lib/LanguageServerWorseReflection/LanguageServerWorseReflectionExtension.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
class LanguageServerWorseReflectionExtension implements Extension
1717
{
18+
const PARAM_UPDATE_INTERVAL = 'language_server_worse_reflection.workspace_index.update_interval';
19+
1820
/**
1921
* {@inheritDoc}
2022
*/
@@ -25,6 +27,12 @@ public function load(ContainerBuilder $container)
2527

2628
public function configure(Resolver $schema)
2729
{
30+
$schema->setDefaults([
31+
self::PARAM_UPDATE_INTERVAL => 100,
32+
]);
33+
$schema->setDescriptions([
34+
self::PARAM_UPDATE_INTERVAL => 'Minimum interval to update the workspace index as documents are updated (in milliseconds)'
35+
]);
2836
}
2937

3038
private function registerSourceLocator(ContainerBuilder $container): void
@@ -45,7 +53,8 @@ private function registerSourceLocator(ContainerBuilder $container): void
4553

4654
$container->register(WorkspaceIndex::class, function (Container $container) {
4755
return new WorkspaceIndex(
48-
ReflectorBuilder::create()->build()
56+
ReflectorBuilder::create()->build(),
57+
$container->getParameter(self::PARAM_UPDATE_INTERVAL)
4958
);
5059
});
5160
}

lib/LanguageServerWorseReflection/Workspace/WorkspaceIndex.php

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Phpactor\WorseReflection\Core\Name;
99
use Phpactor\WorseReflection\Core\Reflector\SourceCodeReflector;
1010
use RuntimeException;
11+
use function Amp\asyncCall;
12+
use function Amp\delay;
1113

1214
class WorkspaceIndex
1315
{
@@ -29,9 +31,25 @@ class WorkspaceIndex
2931
*/
3032
private $documentToNameMap = [];
3133

32-
public function __construct(SourceCodeReflector $reflector)
34+
/**
35+
* @var TextDocument|null
36+
*/
37+
private $documentToUpdate;
38+
39+
/**
40+
* @var bool
41+
*/
42+
private $waiting = false;
43+
44+
/**
45+
* @var int
46+
*/
47+
private $updateInterval;
48+
49+
public function __construct(SourceCodeReflector $reflector, int $updateInterval = 1000)
3350
{
3451
$this->reflector = $reflector;
52+
$this->updateInterval = $updateInterval;
3553
}
3654

3755
public function documentForName(Name $name): ?TextDocument
@@ -49,24 +67,58 @@ public function index(TextDocument $textDocument): void
4967
$this->updateDocument($textDocument);
5068
}
5169

70+
/**
71+
* Refresh the document.
72+
*
73+
* In order to prevent continuous reparsing the document will
74+
* only be refreshed at the sepecified interval.
75+
*/
5276
private function updateDocument(TextDocument $textDocument): void
5377
{
78+
if ($this->waiting) {
79+
$this->documentToUpdate = $textDocument;
80+
return;
81+
}
82+
83+
$this->documentToUpdate = null;
84+
5485
$newNames = [];
5586
foreach ($this->reflector->reflectClassesIn($textDocument) as $reflectionClass) {
5687
$newNames[] = $reflectionClass->name()->full();
5788
}
58-
89+
5990
foreach ($this->reflector->reflectFunctionsIn($textDocument) as $reflectionFunction) {
6091
$newNames[] = $reflectionFunction->name()->full();
6192
}
6293

63-
$this->updateNames($textDocument, $newNames, $this->documentToNameMap[(string)$textDocument->uri()] ?? []);
94+
$this->updateNames(
95+
$textDocument,
96+
$newNames,
97+
$this->documentToNameMap[(string)$textDocument->uri()] ?? []
98+
);
99+
100+
if ($this->updateInterval === 0) {
101+
return;
102+
}
103+
104+
$this->waiting = true;
105+
106+
asyncCall(function () {
107+
yield delay($this->updateInterval);
108+
$this->waiting = false;
109+
110+
if (null === $this->documentToUpdate) {
111+
return;
112+
}
113+
114+
$this->updateDocument($this->documentToUpdate);
115+
});
64116
}
65117

66118
private function updateNames(TextDocument $textDocument, array $newNames, array $currentNames): void
67119
{
68120
$namesToRemove = array_diff($currentNames, $newNames);
69-
121+
70122
foreach ($newNames as $name) {
71123
$this->byName[$name] = $textDocument;
72124
}

phpbench.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"bootstrap": "vendor/autoload.php",
3-
"path": "tests/LanguageServerCodeTransform/Benchmark"
3+
"path": [
4+
"tests/LanguageServerCodeTransform/Benchmark",
5+
"tests/LanguageServerWorseReflection/Benchmark"
6+
]
47
}

tests/LanguageServerCompletion/IntegrationTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected function createTester(): LanguageServerTester
5252

5353
LanguageServerBridgeExtension::class,
5454
], [
55-
FilePathResolverExtension::PARAM_APPLICATION_ROOT => __DIR__ .'/../../'
55+
FilePathResolverExtension::PARAM_APPLICATION_ROOT => __DIR__ .'/../../',
5656
]);
5757

5858
$builder = $container->get(LanguageServerBuilder::class);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Phpactor\Extension\LanguageServerWorseReflection\Tests\Benchmark;
4+
5+
use PhpBench\Benchmark\Metadata\Annotations\Executor;
6+
use PhpBench\Benchmark\Metadata\Annotations\OutputTimeUnit;
7+
use PhpBench\Benchmark\Metadata\Annotations\Revs;
8+
use Phpactor\Extension\LanguageServerCompletion\Tests\IntegrationTestCase;
9+
use Phpactor\LanguageServer\Test\LanguageServerTester;
10+
use PhpBench\Benchmark\Metadata\Annotations\ParamProviders;
11+
use Generator;
12+
13+
/**
14+
* @BeforeMethods({"setUp"})
15+
* @OutputTimeUnit("milliseconds")
16+
* @Executor("local")
17+
*/
18+
class WorkspaceIndexBench extends IntegrationTestCase
19+
{
20+
/**
21+
* @var LanguageServerTester
22+
*/
23+
private $tester;
24+
25+
public function setUp(): void
26+
{
27+
$this->tester = $this->createTester();
28+
$this->tester->initialize();
29+
$this->tester->textDocument()->open('file:///foobar', '');
30+
}
31+
32+
/**
33+
* @ParamProviders({"provideUpdate"})
34+
* @Revs(10)
35+
* @Iterations(10)
36+
*/
37+
public function benchUpdate(array $params): void
38+
{
39+
$this->tester->textDocument()->update('file:///foobar', $params['text']);
40+
}
41+
42+
public function provideUpdate(): Generator
43+
{
44+
$source = mb_str_split((string)file_get_contents(
45+
__DIR__ . '/source/source.php.example'
46+
), 1);
47+
48+
$buffer = '';
49+
50+
foreach ($source as $index => $char) {
51+
$buffer .= $char;
52+
if (0 === $index % 1000) {
53+
yield 'length: ' . strlen($buffer) => [
54+
'text' => $buffer
55+
];
56+
}
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)